摘要:当Java桌面程序开发完成做产品的时候,面对未知的安装环境,通常是编写一些预安装检测脚本/程序,让程序傻瓜化安装以便减少分发出去的产品带来
的未知工作量(安装答疑,操作系统问题引起安装失败等),当然你也可以把安装过程中出现的问题写入文档手册。本文写作的背景是
1.不想让客户知道产品是用Java编写的
2. 客户懒的帮助文档都不想看
截至本文编写的时候,JDK官方最新版本为jdk 8u144,本文测试环境在虚拟机Windows XP,使用的版本为jdk-8u144-windows-i586.exe
Java 安装是使用 Microsoft Window Installer (MSI) 2.0 技术构建的。MSI 包含对静默或无人参与安装的内置支持。
从Google搜索"java silent install"总结出了一些安装参数
/lang=<语言代码> 安装特定语言包
支持的语言包如下
语言 | 语言代码 |
英语 (en) | 1033 |
法语 (fr) | 1036 |
德语 (de) | 1031 |
意大利语 (it) | 1040 |
日语 (ja) | 1041 |
韩语 (ko) | 1042 |
西班牙语 (es) | 1034 |
瑞典语 (sv) | 1053 |
简体中文 (zh) | 2052 |
繁体中文 (zh_TW) | 1028 |
经过笔者实测,设置语言代码为1041,即日语。安装命令:
jdk-8u144-windows-i586.exe /lang=1041
安装包界面如下
做对日外包的同学应该习惯于看到这个界面。这样就以日语语言环境安装JDK了。
WEB_JAVA = 1|0 Java浏览器支持 1启用 0禁用
IEXPLORER = 1|0 IE浏览器支持 1启用 0 禁用(jdk1.8命令行实测无效)
SPONSORS= 1|0 绕过赞助商,如浏览器插件,实测发现完整安装了JDK
WEB_JAVA_SECURITY_LEVEL = VH|H|M|L 浏览器中运行的未签名 Java 应用程序的安全级别 非常高|高|中|低
AUTO_UPDATE= 1|0 JDK自动检测更新 1启用 0禁用
NOSTARTMENU= 1|0 创建开始菜单 1禁用 0 启动
当然你也可以安装后在控制面板找到Java设置安全级别
以上为JDK 7u10 release版本起的安装特性,Java早期的时候有一些应用就是通过Web Applet方式发布的,笔者工作早些年的时候一
家做ERP的单位他就是通过浏览器方式发布项目的,现今已经很少看到这样的企业应用,鲜见一些社区里的Demo程序会有Web Applet的示例程序。
/L install.log 记录安装日志
常规情况下Oracle公司已经帮我考虑到安装过程中遇到的问题,并且他也有自己的解决方法,这些都封装在安装包里,当然有时候如果需要查看java安装过程的操作,
或者在特殊情况下(静默安装)他都做了什么,或者是为了排错,我们需要记录日志。/L参数便应运而生。
/s 静默安装
此参数不需要赋值,直接带入安装命令行即可,启动后将在后台默默的安装直至安装完成结束。
ADDLOCAL = [[ToolsFeature],[SourceFeature],[PublicjreFeature]] 选择附加组件包各个参数可独立使用也可以组合使用
ADDLOCAL = ["ToolsFeature,SourceFeature,PublicjreFeature"] 安装开发工具/源代码/JRE
ADDLOCAL = ["ToolsFeature,SourceFeature"] 安装开发工具和源代码
除了以上命令还有很多配置方式或者参数,笔者没有一一测试,笔者最终的安装命令如下:
jdk-8u144-windows-i586.exe /lang=2052 /s /L c:jdk-install.log INSTALLDIR=D:developJavajdk1.8.0_144 ADDLOCAL="ToolsFeature,SourceFeature,,PublicjreFeature" WEB_JAVA=0 AUTO_UPDATE=0 NOSTARTMENU=1 /INSTALLDIRPUBJRE=D:developJavajre1.8.0_144
命令解释:笔者选择了简体中文静默安装,记录了安装日志到c:jdk-install.log,JDK安装目录c:\embededJDK1.8,另外还安装了开发工具和源代码组件包、jre,禁用浏览Java插件,关闭JAVA自动更新,屏蔽了java开始菜单的创建。
安装后效果图如下:
从IE浏览器“工具”--"管理加载项"找不到java浏览器插件
开始菜单也没了Java的踪迹
windows注册表启动项也没有java的影子
当然文字不是到这里就结束了。
本文最终目的是要发送福利,在下面,笔者用C#写了简单的程序来静默安装JDK。
首先从java官方获得JDK的x86和x64版本分别改名为sdk-x86.bin和sdk-x64.bin
下面奉上代码(考虑客户端实际安装环境,为兼容Windows XP SP及更高版本,代码采用C#2.0 .NET Framework 2.0)
using System; using System.IO; using System.ComponentModel; using System.Reflection; using System.Text.RegularExpressions; using System.Diagnostics; using System.Threading; namespace Starter { class Enterance { static string JVM_Pattern = string.Empty; static bool hasFoundJVM = false; static string Target_JDK_FIlE = string.Empty; public static void Main(string[] args) { Console.WriteLine("按任意键开始安装xxx综合平台..."); Console.ReadKey(true); Console.Clear(); if(isFileMissed()) { Console.WriteLine("安装文件丢失请联系技术人员"); }else{ doPreInstallCheck(); if(hasFoundJVM){ installJVM(Target_JDK_FIlE); } } Console.ReadKey(true); } /// <summary> /// 判断主安装程序是否丢失 /// </summary> /// <returns></returns> static bool isFileMissed() { return !File.Exists("setup.exe"); } static String getArch() { return System.Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE", EnvironmentVariableTarget.Machine); } /// <summary> /// 安装预检测 /// </summary> static void doPreInstallCheck(){ String arch =getArch(); Architecture type= (Architecture)Enum.Parse(typeof(Architecture),arch); switch(type){ case Architecture.AMD64: case Architecture.IA64: case Architecture.x64: Console.WriteLine("发现{0}",EnumUtil.GetEnumDescription(type)); JVM_Pattern = "sdk.*x64.*"; break; case Architecture.x86: Console.WriteLine("发现{0}",EnumUtil.GetEnumDescription(type)); JVM_Pattern = "sdk.*x86.*"; break; } string []files = Directory.GetFiles(Environment.CurrentDirectory,"sdk-*.bin"); Action<string> action = new Action<string>(matchJVM); Array.ForEach(files, action); } private static void matchJVM(string jdkfile) { FileInfo fileInfo = new FileInfo(jdkfile); bool isMatched = Regex.IsMatch(fileInfo.Name,JVM_Pattern); if(isMatched) { hasFoundJVM = true; Target_JDK_FIlE = jdkfile; } if(Debugger.IsAttached) Console.WriteLine("{0}与当前操作系统匹配的JVM:{1}",fileInfo.Name,isMatched); } /// <summary> /// 安装JVM /// </summary> /// <param name="fileName">JDK文件名</param> static void installJVM(String fileName) { Console.WriteLine("即将安装程序,等待时间取决你电脑的性能"); //https://stackoverflow.com/questions/3360555/how-to-pass-parameters-to-threadstart-method-in-thread Thread thread =new Thread(new ParameterizedThreadStart(DoSlientInstall)); thread.Start(fileName); Thread.Sleep(5000);//等待安装进程启动 FileInfo fileInfo = new FileInfo(fileName); InstallState state =new InstallState(); state.processName =fileInfo.Name; Timer timer =new Timer(waitForProcessExitCallBack,state,0,1000); state.tmr = timer; } public static void waitForProcessExitCallBack(object target){ InstallState state = target as InstallState; Timer timer = state.tmr; Process []allProcceses = Process.GetProcesses(); bool isJDKProcExit = true; foreach(Process proc in allProcceses) { string procName = String.Format("{0}",proc.ProcessName); if(Debugger.IsAttached) Console.WriteLine("{0},{1}, {2}",procName,state.processName,procName.Equals(state.processName)); if(procName.Equals(state.processName)){ isJDKProcExit = false; } } if(isJDKProcExit){ Console.WriteLine(" 安装所需软件使用时间:{0}秒",state.counter); timer.Dispose(); }else{ Console.Write(state.delims); state.counter++; //Thread.Sleep(1000); // //timer.Change(1000,1000); //timer.Dispose(); } } //https://stackoverflow.com/questions/24918768/progress-bar-in-console-application //https://stackoverflow.com/questions/12354883/how-do-i-gracefully-stop-a-system-threading-timer static void DoSlientInstall(object fileName) { Console.WriteLine("安装进行中,请勿退出..."); ProcessStartInfo pStartInfo = new ProcessStartInfo(); pStartInfo.FileName = (String)fileName; pStartInfo.Verb = "runas"; pStartInfo.Arguments = "/lang=2052 /s /L c:\jdk-install.log INSTALLDIR=c:\embededJDK1.8 ADDLOCAL="ToolsFeature,SourceFeature" WEB_JAVA=0 AUTO_UPDATE=0 NOSTARTMENU=1"; pStartInfo.UseShellExecute = false; System.Diagnostics.Process.Start(pStartInfo); //https://stackoverflow.com/questions/6050478/how-do-i-create-edit-a-manifest-file //System.Diagnostics.Process.Start((String)fileName,"/s"); } } //http://www.gnu.org/software/dotgnu/pnetlib-doc/System/Threading/Timer.html class InstallState{ /// <summary> /// 时间计数器 /// </summary> public int counter = 0; /// <summary> /// 进程名 /// </summary> public String processName; /// <summary> /// 分隔符 /// </summary> public string delims="*"; public Timer tmr; } }
using System; using System.ComponentModel; using System.Reflection; using System.Collections.Generic; using System.Data.SqlClient; using System.Data; namespace Starter { /// <summary> /// 操作系统架构枚举类 /// </summary> //https://msdn.microsoft.com/en-us/library/aa384274.aspx //https://ss64.com/nt/syntax-64bit.html public enum Architecture{ [Description("64位操作系统")] x64 =0, [Description("AMD 64位操作系统")] AMD64=1, [Description("Intel64位操作系统")] IA64=2, [Description("x86架构操作系统")] x86=3 } class EnumUtil{ public static string GetEnumDescription(Enum enumValue) { string enumValueAsString = enumValue.ToString(); Type type = enumValue.GetType(); FieldInfo fieldInfo = type.GetField(enumValueAsString); object[] attributes = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), false); if (attributes.Length > 0) { DescriptionAttribute attribute = (DescriptionAttribute)attributes[0]; return attribute.Description; } return enumValueAsString; } } }
安装效果图
参考资料
Installing With a Configuration File