分享

一键安装IIS的点点滴滴——For所有Microsoft的操作系统(上)

 kittywei 2011-11-03

一键安装IIS的点点滴滴——For所有Microsoft的操作系统(上)

临近公司的软件要完工了,最近几天一直在泉哥的带领下为我们公司的产品做IIS一键安装的程序,在这个过程中也学习到狠多有用的知识,现在拿来与大家分享一下,也顺便可以巩固一下自己的知识。

程序要求:

  • 适合于所有Microsoft操作系统,程序能自动识别操作系统的版本和型号,安装和配置对应的IIS
  • 用户只需要执行程序,便不需再做任何事情,程序的安装是静默安装,安装过程不需要用户做任何操作

首先我们知道,Windows 7 以前的操作系统,我们安装IIS都需要安装光盘。光盘里必然会有一个i386的文件夹,文件夹大概有几百兆,里面放着我们需要的IIS安装文件。以往我们装IIS都是在控制面板中点添加程序组件,然后勾选上Internet信息服务,这时系统会自动搜索光盘中的i386文件夹,然后找到安装IIS所需要的文件,自己安装起来,最后再提示我们安装成功。如果要卸载IIS,则用同样的操作,不同的只是把Internet信息服务前面的勾勾取消掉而已。这样一个过程,需要用户自己做很多事情,面对许多对电脑不熟悉的用户,我们很难要求他们自己去完成这些事情,因此才有了一键安装IIS程序的诞生。其实现在已经有很多关于一键安装IIS的软件了,有bat格式的,也有exe格式的。当然我自己的程序也是借鉴了很多网上类似程序的思路和原理,我在以后的篇幅中会尽量都标记上别人文章的引用。

接下来我们需要分析一下如何实现程序。首先,我们的安装是不可能要光盘的,所以我们第一步是要把光盘中的i386文件夹拷贝出来。但经过分析,在i386文件中只有少量的文件是我们在安装IIS时需要的,所以我们第二步是要从i386文件中提取出我们需要的少量文件。最后我们再通过命令行来完成我们的IIS安装。

程序实现步骤:

  • 从光盘提取i386文件(如果是64位系统还需要提取AMD64文件)
  • 从i386(AMD64)文件找到所需的IIS安装文件
  • 执行命令行完成IIS安装

步骤上看起来很简单,但是做的时候遇到很多大大小小的问题,我想读者在做的过程中可能或多或少也会遇见跟我差不多的问题,所以详情且看下文:

 我最先做测试的是32位的XP系统,首先从微软官网下载了一个原始的XP Professional系统安装文件,安装到虚拟机。然后从虚拟机中拷贝出i386的文件夹到本地。

问题1:在i386文件夹中,哪些文件才是我们装IIS需要的呢?

回答:i386文件夹中有一个iis.in_文件,这个"iis.in_"是个压缩文件,i386中的所有文件都是经过压缩的,所以你会看见基本上所有文件的后缀名的最后一位都是"_"。而i386文件夹里面自己提供了一个叫做expand.exe的解压缩程序,于是我们可以执行命令行,如下图:

第一个红线是被解压的文件,第二个红线是解压到的目标文件,现在我们就把iis.inf文件放在了D盘,找到文件打开来一看,里面有大量的信息,其中里面有很多文件的名字,这些文件都是在i386中能找到的,如图:

这些文件我想很可能就是我们装IIS所需要的

(因为这个文本文件名字就叫IIS.inf嘛,:-))。

因此我们下一步的工作是编一个程序,从这个已有的iis.inf文件中搜索到所有的文件名,只要这些文件名是i386里面存在的,则拷贝出来。

问题2:但是这里面的文件名太多,我们怎么查找呢?

回答:其实IIS的安装需要的文件很多,但是需要的文件后缀名类型却可以枚举,所以我们只需要通过试探出传统IIS安装过程中所需要的所有文件的后缀名,于是我们就可以编程取出inf文件中的所有与这些后缀名相符的文件名即可。代码如下:

 

代码
   internal class IISManager
    {
        
/// <summary>
        
/// 安装文件名列表
        
/// </summary>
        protected List<string> files = new List<string>();

        
public string OperatingSystem { getset; }
        
private string[] requiredSuffix;
        
/// <summary>
        
/// 需要的安装文件后缀
        
/// </summary>
        protected string[] RequiredSuffix
        {
            
get
            {
                
if (requiredSuffix == null)
                {
                    
switch (OperatingSystem)
                    {
                        
case "Microsoft Windows XP":
                            requiredSuffix 
= new string[] { ".dll"".ini"".h2"".exe"".ocx"".msc"".pmc"".sql"".cab"".vbs"".hlp"".cnt" };
                            
break;
                        
case "Microsoft Windows Server 2003":
                        case "Microsoft Windows Server 2003 R2":
                            requiredSuffix 
= new string[] { ".dll"".ini"".h2"".exe"".ocx"".msc"".pmc"".sql"".cab"".vbs"".hlp"".cnt"".wsc"".mfl"".mof"".asp" };
                            
break;
                    }
                }
                
return requiredSuffix;
            }
            
set
            {
                requiredSuffix 
= value;
            }
        }
        
private char[] separatorChars = new char[] { ',''|'';''\t''\\''/'' ''\"' };
        
/// <summary>
        
/// Inf文件内容分隔符
        
/// </summary>
        protected char[] SeparatorChars
        {
            
get { return separatorChars; }
            
set { separatorChars = value; }
        }

        
protected string IISInfPath { getset; }
        
protected string I386PackSourcePath { getset; }
        
protected string I386BatPath { getset; }

        
/// <summary>
        
/// 获取IIS必需文件
        
/// </summary>
        
/// <param name="operatingSystem">操作系统名称</param>
        
/// <param name="infPath">Inf文件路径</param>
        
/// <param name="sourcePath">I386源文件路径(路径需包含\i386)</param>
        
/// <param name="batPath">要生成的Bat文件路径</param>
        internal IISManager(string operatingSystem, string infPath, string sourcePath, string batPath)
        {
            
this.OperatingSystem = operatingSystem;
            
this.IISInfPath = infPath;
            
this.I386PackSourcePath = sourcePath;
            
this.I386BatPath = batPath;
        }

        
#region GetRequiredFiles
        
/// <summary>
        
/// 查找文件是否存在(包含:.DLL->.DL_或者.DLL)
        
/// </summary>
        
/// <param name="sourcePath">查找目标路径</param>
        
/// <param name="fileName">被查找的文件名</param>
        
/// <returns>返回查找到的文件路径,如果没找到返回空字符串</returns>
        private string FindFile(string sourcePath, string fileName)
        {
            
if (fileName.EndsWith(".h2"))
            {
                fileName 
+= "_";
            }
            
string[] strList = Directory.GetFiles(sourcePath, fileName, SearchOption.AllDirectories);
            
if (strList != null && strList.Length > 0)
                
return strList[0];
            fileName 
= fileName.Substring(0, fileName.Length - 1+ "_";
            strList 
= Directory.GetFiles(sourcePath, fileName, SearchOption.AllDirectories);
            
if (strList != null && strList.Length > 0)
                
return strList[0];
            
return "";
        }
        
/// <summary>
        
/// 获得IIS安装必备文件并写入Bat文件中
        
/// </summary>
        internal string GetRequiredFiles()
        {
            
//获取IIS必需文件
            string[] iisInfPath = File.ReadAllLines(IISInfPath);
            
for (int i = 0; i < iisInfPath.Length; i++)
            {
                
string[] separatorList = iisInfPath[i].Split(SeparatorChars);
                
for (int j = 0; j < separatorList.Length; j++)
                {
                    
foreach (string suffix in RequiredSuffix)
                    {
                        
if (separatorList[j].Contains(suffix))
                        {
                            files.Add(separatorList[j]);
                        }
                    }
                }
            }
            
//写入Bat文件
            int count = 0;
            
for (int i = 0; i < files.Count; i++)
            {
                
string filePath = FindFile(I386PackSourcePath, files[i]);
                StringBuilder batStr 
= new StringBuilder();
                
if (!string.IsNullOrEmpty(filePath))
                {
                    
//File.Copy(filePath, targetPath);
                    batStr.AppendLine(string.Format("copy %1\\{0} %2\\{1} /y", filePath.Substring(filePath.IndexOf(I386PackSourcePath)), files[i]));
                    count
++;
                }
                File.WriteAllText(I386BatPath, batStr.ToString());
            }
            
return count + "/" + files.Count;
        }
        
#endregion
    }

 

PS:以前我是用的File.Copy来拷贝文件,后来泉哥说这样不好,该为只是生成一个bat文件,里面存放了拷贝文件的命令,手动运行来拷贝文件,这样的好处是一旦需要手动修改某个单独的文件拷贝,可以直接编辑bat文件来实现,而不用改动程序。于是程序就变成这样了。这里的"%1、%2"是到时候接受命令行的参数。

这里代码中的后缀名都是经过反复检验得知的,Windows Server 2003和Windows Server 2003 R2一样,但是和Windows XP的后缀名要求不一样。

Program程序代码如下:

 

代码
 class Program
    {
        
static void Main(string[] args)
        {
            Console.WriteLine(
@"请输入以下参数:");
            Console.WriteLine(
@"1.操作系统名称:");
            
string operatingSystemName = Console.ReadLine();
            Console.WriteLine(
@"2.Inf文件路径:");
            
string infPath = Console.ReadLine();
            Console.WriteLine(
@"3.I386源文件路径(路径需包含\i386):");
            
string sourcePath = Console.ReadLine();
            Console.WriteLine(
@"4.要生成的Bat文件路径:");
            
string batPath = Console.ReadLine();

            Console.WriteLine(
"{0}:开始查找文件,请稍后...",DateTime.Now);
            IISManager iisManager 
= new IISManager(operatingSystemName, infPath, sourcePath, batPath);
            Console.WriteLine(
"{0}:成功发现{1}个文件!",DateTime.Now, iisManager.GetRequiredFiles());
            Console.WriteLine(
"{0}:程序结束!", DateTime.Now);
        }
    }

 

 

执行了生成的bat文件后,我们便获得了IIS安装需要的所有安装文件了,不再需要光盘了。

另外这里要注意:在64位操作系统的光盘中,IIS安装文件不只是在i386文件夹中,还有大部分在AMD64文件夹中。因此我们需要把光盘中的i386文件夹中的文件和AMD64文件夹中的文件都搞出来,不然以后安装IIS的时候会提示找不到在AMD64文件夹下的文件。

问题3:如何静默安装IIS?

回答:这个问题最初我也不知道,于是在网上找了找,找到一篇:http://hi.baidu.com/2fred/blog/item/824cd9b48dfeb57b8bd4b237.html 文章。

这篇文章的Windows 7的IIS安装很有用,我们后面的Windows 7 IIS安装就用的他的方法,但是XP的脚本不是很理解,2003的IIS也可没有提供,因此又找到了几篇微软的文章:

http://support.microsoft.com/kb/222444http://support.microsoft.com/kb/309506

这两篇文章都是泉哥找到的,这也让我更深刻的觉得,好的程序员往往都有能快速搜索到有效资源的能力,这方面我真还得多多锻炼和学习。^_^

这两篇文章里清晰地说明了IIS的安装命令,即用到系统自带的sysocmgr.exe程序:

sysocmgr /q /i:%windir%\inf\sysoc.inf /u:c:\ocm.txt

此处要注意的是/u:后面的路径c:\ocm.txt,这个文件是安装IIS时的配置,也就是哪些要安装,哪些不需要安装,这个txt文件的内容别人网站也给出来了:

[Components]
iis_common = ON
iis_www = ON
iis_www_vdir_scripts = ON
iis_inetmgr = ON
fp_extensions = OFF

iis_ftp = OFF

这里最后两个设置为OFF与原文不一样,不过这样不影响,因为我们只需要安装最基本的组件。

因此我们的思路就是把上面的Components文本放在一个txt文件中,然后让程序执行绿色那行的命令,IIS即安装成功。

此处我试探性的把所有的选项都设置为OFF,运行之后发现IIS被卸载,于是卸载IIS也如此简单!

这里让.Net执行命令行也在网上查了查方法,代码如下:

 

代码
        /// <summary>
        
/// 执行命令行
        
/// </summary>
        
/// <param name="optionalFilePaths">命令</param>
        
/// <returns>返回结果</returns>
        protected virtual string ExecuteCmd(string[] optionalFilePaths)
        {
            
//运行命令行
            Process p = new Process();
            
// 设定程序名
            p.StartInfo.FileName = "cmd.exe";
            
// 关闭Shell的使用
            p.StartInfo.UseShellExecute = false;
            
// 重定向标准输入
            p.StartInfo.RedirectStandardInput = true;
            
// 重定向标准输出
            p.StartInfo.RedirectStandardOutput = true;
            
//重定向错误输出
            p.StartInfo.RedirectStandardError = true;
            
// 设置不显示窗口
            p.StartInfo.CreateNoWindow = true;
            
// 启动进程
            p.Start();
            
for (int i = 0; i < optionalFilePaths.Length; i++)
            {
                p.StandardInput.WriteLine(optionalFilePaths[i]);
            }
            p.StandardInput.WriteLine(
"exit");
            
// 从输出流获取命令执行结果
            string strRst = p.StandardOutput.ReadToEnd();
            p.WaitForExit();
            p.Close();
            
return strRst;
        }

 

方法的返回值是输出结果,传入值是需要执行的命令数组,这里我们只需要将绿色那行命令传入此方法即可,但记得修改最后的文本文件路径噢!

在我的程序中我写了两个文本文件,一个是安装IIS的,一个是卸载IIS的,只需要在方法中变更对应传入的文本文件路径即可。

XP测试是通过了,下面还需要测试XP的SP1、2、3版本。我只举例说明SP1版本,后面的版本方法类似。

我们知道SP1版本中IIS肯定是有所更新,那么我们如何正确的更新以前我们拷贝出来的原版的i386文件呢?这时就要用到文件的集成。

问题4:如何集成Windows ServicePack中的文件?

回答:这个我也在网上找到了方法:http://masida.blog.51cto.com/719236/168928

执行SP光盘i386中update文件夹中的update方法:

integrate表示集成,后面的路径就填写之前我们拷贝出来的原版i386文件。执行后SP1中更新的的i386文件就替换了原版的i386文件,而没有更新的则不会改变。

i386文件更新后,我们则需要重新再重复以前的步骤,解压缩出iis.inf文件-->拷贝出对应后缀名的文件-->安装IIS。

SP2、SP3同理。

最后我们得到的i386文件就是最新的版本,这个时候我们将这个最新版本的IIS安装文件放在原版XP、XP SP1、XP SP2、XP SP3中测试,发现IIS安装能够成功,于是最后我们的程序只需要一个最新版本的i386文件夹(此文件夹里放置的是拷贝出来的IIS安装必需的文件,大概16.3M);两个文本文件:一个是安装IIS的配置、一个是卸载IIS的配置;然后一个exe控制台程序。控制台程序代码将在后面一起发放。

另外,安装好IIS之后,还需要在IIS中注册ASP.NET。

问题5:如何注册Asp.Net?

回答:有一个aspnet_regiis工具,专门就是做这件事的。参考http://zhengmeishuang./blog/599036,具体方法是:在命令行中输入:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis -i

此处还需注意一点:如果是64位的操作系统,则命令有点不一样,http://www./read.php/284.htm

C:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\aspnet_regiis -i

即可注册成功,此操作需在安装好IIS之后执行。这里我简单解释一下,该命令前面的路径是系统盘所在的路径,因为客户的系统不一定都是安装在C盘,所以编程时需要利用环境变量查看操作系统所在盘符。中间的v2.0.50727是要注册的Asp.Net的版本号,可以根据需要改变,所以在程序中,我们的代码如下:

 

代码
        /// <summary>
        
/// 在ASP.NET中注册IIS
        
/// </summary>
        protected void RegIISForAspnet()
        {
            
if (CheckOSBitness.Is64BitOperatingSystem())
                ExecuteCmd(Path.Combine(System.Environment.GetEnvironmentVariable(
"windir"), @"Microsoft.Net\Framework64\v2.0.50727\aspnet_regiis -i"));
            
else
                ExecuteCmd(Path.Combine(System.Environment.GetEnvironmentVariable(
"windir"), @"Microsoft.Net\Framework\v2.0.50727\aspnet_regiis -i"));
        }

 

 

windir的变量就是用来替代"C:\Windows",把这个方法放在装好IIS之后执行即可!

如果以我们目前的程序运行,你会发现在安装IIS时,系统会弹出对话框让你选择i386文件夹的路径,这也就是说系统在默认的文件夹里没有找到我们的i386安装文件夹。因此我们还需要做一个工作,就是在安装之前把默认查找安装文件的路径该为我们程序的i386文件夹路径,然后在安装完成之后再把路径改回来。

问题6:如何更改默认查找i386文件夹的路径?

回答:用程序修改在注册表中默认路径的值。参考http://forum./windows/ServicePackCachePath-ServicePackSourcepath-Source-Path-ftopict278557.html

在注册表中找到HKLM\SOFTWARE\Microsoft\Windows\Setup。我们只需要修改其中两个关键的值:ServicePackSourcePath和SourcePath。在修改时要注意,此路径不应包含i386的名字,因该是我们的i386文件夹的父目录的路径,即如果我们的i386文件夹路径为D:\OnClickIIS\I386,那么我们应把注册表中的路径该为:D:\OnClickIIS即可: 

代码
 /// <summary>
        
/// 修改安装查找路径
        
/// </summary>
        protected void AmendRegeditPath()
        {
            RegistryKey pRegKey 
= Registry.LocalMachine;
            pRegKey 
= pRegKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Setup"true);
            regServicePackSourcePath 
= pRegKey.GetValue("ServicePackSourcePath").ToString();
            regSourcePath 
= pRegKey.GetValue("SourcePath").ToString();
            pRegKey.SetValue(
"ServicePackSourcePath", I386PackPath.Substring(0, I386PackPath));
            pRegKey.SetValue(
"SourcePath", I386PackPath.Substring(0, I386PackPath));
        }
        
/// <summary>
        
/// 恢复安装查找路径
        
/// </summary>
        protected void ReStoreRegeditPath()
        {
            RegistryKey pRegKey 
= Registry.LocalMachine;
            pRegKey 
= pRegKey.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Setup"true);
            pRegKey.SetValue(
"ServicePackSourcePath", regServicePackSourcePath);
            pRegKey.SetValue(
"SourcePath", regSourcePath);
        }

 

这里OpenSubKey里的true参数表示要让注册表可写,如果是该为false,则我们执行后面的SetValue操作时会抛异常。这里我用了两个变量存储原来注册表中的路径值,在结束时再把值还原回去,为了保证每次程序都能够还原注册表,我选择把ReStoreRegeditPath方法放在了Finally中:

 

代码
        /// <summary>
        
/// 开始安装IIS
        
/// </summary>
        internal override void InstallIIS()
        {
            
try
            {
                AmendRegeditPath();

                ExecuteCmd(SysocmgrCmd 
+ "\"" + InOptionalFilePath + "\"");

                RegIISForAspnet();
            }
            
catch (Exception)
            {
                
throw;
            }
            
finally
            {
                ReStoreRegeditPath();
            }
        }
        
/// <summary>
        
/// 开始卸载IIS
        
/// </summary>
        internal override void UnInstallIIS()
        {
            ExecuteCmd(SysocmgrCmd 
+ "\"" + UnOptionalFilePath + "\"");
        }

 

在卸载IIS时则不用修改路径,因为卸载时用不到i386文件夹。

此时,XP各个版本的IIS安装都可以通过这个程序来完成了!

再继续测试Windows Server 2003,我发现原来2003的安装IIS方法和XP基本一样,所以我们的程序不需要做任何改动就可以在2003上面运行,只是2003的i386文件需要我们用和XP一样的方法搞出来,还有就是2003的安装配置文件也有一点点不同:

[Components]
iis_common = ON
iis_www = ON
iis_asp = ON
iis_inetmgr = ON
aspnet= ON

之后在Windows Server 2003 R2 SP2、Windows Server 2003 R2 x64 SP2的版本测试也是顺利通过,此处要注意:2003和2003 R2和2003 R2 x64的版本的i386文件都不一样,不能偷懒,必须都用上面的方法从光盘的到,另外2003 R2 x64版本除了有i386文件,还需要有AMD64文件。

因此现在我们的程序已经能够顺利在XP(原版、SP1、SP2、SP3)和2003(原版、SP1、SP2)以及2003 R2(32位和64位的SP2)测试通过了^_^

因为程序是用Visual Studio 2008做的,所以要成功运行程序需要系统先装上.NetFramwork3.5,而安装.NetFramwork3.5对于系统的要求是:

Windows XP:SP2及以上

Windows Server 2003(R2或非R2):SP1及以上

这一篇篇幅太长了,因此我分为2篇来完成。在下一篇中我将主要阐述Windows 7的IIS安装以及如何自动识别用户操作系统来调用正确的安装方法!

接着上一篇的讲,下面我们将讨论Windows7、Windows Server 2008 以及Windows Vista的IIS安装:

Windows7的IIS的安装文件其实在装系统的时候就已经拷贝在电脑里了的,这也就是说我们不需要再那么费劲搞出i386文件了,这对我们来说是很方便的(Windows Server 2008 和Windows Vista也是这样)。我们只需要执行安装的命令就可以了。具体安装有两种方式:

1.一种比较简单,是通过

start /w pkgmgr /iu:IIS-WebServerRole;IIS-WebServer;IIS-CommonHttpFeatures;IIS-StaticContent;IIS-DefaultDocument;IIS-DirectoryBrowsing;IIS-HttpErrors;IIS-HttpRedirect;IIS-ApplicationDevelopment;IIS-ASPNET;IIS-NetFxExtensibility;IIS-ASP;IIS-ISAPIExtensions;IIS-ISAPIFilter;IIS-ServerSideIncludes;IIS-HealthAndDiagnostics;IIS-HttpLogging;IIS-LoggingLibraries;IIS-RequestMonitor;IIS-HttpTracing;IIS-CustomLogging;IIS-ODBCLogging;IIS-Security;IIS-BasicAuthentication;IIS-WindowsAuthentication;IIS-DigestAuthentication;IIS-ClientCertificateMappingAuthentication;IIS-IISCertificateMappingAuthentication;IIS-URLAuthorization;IIS-RequestFiltering;IIS-IPSecurity;IIS-Performance;IIS-WebServerManagementTools;IIS-ManagementConsole;IIS-ManagementScriptingTools;IIS-ManagementService;IIS-IIS6ManagementCompatibility;IIS-Metabase;IIS-WMICompatibility;IIS-LegacyScripts;IIS-LegacySnapIn;WAS-WindowsActivationService;WAS-ProcessModel;WAS-NetFxEnvironment;WAS-ConfigurationAPI

的命令来执行。关键是前面的命令,pkgmgr就是Windows 7的IIS安装命令。/iu:是安装配置,如果是要卸载IIS,则直接把/iu:该为/uu:即可!后面的一大串名字都是表明需要安装哪些文件,打开Windows 7的Windows功能面板,你会看见Internet信息服务的选项,这些选项都是Checkbox,如果是要安装IIS,直接在需要安装的选项前面打勾就行了,这行命令后面的一大串文件名的意思也就是表示要在哪些选项前面打上勾而已。

2.第二种方式比较复杂,但是我最开始没有发现第一种方法,就用的这一种,整个过人让人纠结啊~~~,先找到的资料是:http://learn./page.aspx/133/using-unattend-setup-to-install-iis-7/,他用的方法是执行命令:

start /w pkgmgr /n:unattend.xml

这里的unattended.xml文件内容是:

<?xml version="1.0" ?>
<unattend xmlns="urn:schemas-microsoft-com:unattend"  
    xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State">
<servicing>
   <!-- Install a selectable update in a package that is in the Windows Foundation namespace -->
   <package action="configure">
      <assemblyIdentity
         name="Microsoft-Windows-Foundation-Package"
         version="6.0.5308.6"
         language="neutral"
         processorArchitecture="x86"
         publicKeyToken="31bf3856ad364e35"
         versionScope="nonSxS"
      />
    <selection name="IIS-WebServerRole" state="true"/>
    <selection name="WAS-WindowsActivationService" state="true"/>
    <selection name="WAS-ProcessModel" state="true"/>
    <selection name="WAS-NetFxEnvironment" state="true"/>
    <selection name="WAS-ConfigurationAPI" state="true"/>
  </package>
</servicing>
</unattend>
这里标记上红色字体的就是最让我纠结的地方:

(1)先说x86。它表示你正在运行中的操作系统的位数,一般来说有两种类型:amd64和x86。大家应该都知道,amd64表示64位操作系统,x86表示32位操作系统。大家注意一下,这里所说的位数不是指处理器硬件,而是说我们安装的操作系位数。举个简单的例子,如果我们买的电脑是32位处理器,那么只能装32位操作系统(但现在市场上应该大部分都是64位处理器了)。如果电脑是64位处理器,则可以安装32位操作系统或者64位操作系统。在Win7下或者Win2008下可以通过运行cmd,执行systeminfo命令来查看自己的系统信息:

大家可以看到,这里系统类型说的就是我安装的操作系统是64位的,硬件处理器同时也是64位的。

那么根据客户的操作系统位数不同,我们的xml文件自然也要相应改变。

问题6:如何通过程序判断客户机的操作系统位数呢?

回答:我第一个想法是查看注册表,在HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\WINDOWS NT\CURRENTVERSION的BuildLabEx中我找到了想要的:

但是泉哥说了,最好不要通过查看注册表中的值内容来实现,于是只能换个方法。

他提到了一点是,64位操作系统下必定会有HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node文件,如果是32位系统则不会有,于是我们便测试了一下,发现这个判断有个严重问题,他根本就不是判断所在操作系统的位数,而是运行的程序的位数,例如,我们如果把Visual Studio上方的x64改为x86,那么运行时的程序就会找不到注册表中刚才那个Wow6432Node文件,相反如果改为x64运行,则找得到。所以这和具体操作系统位数并无直接关系,此方法不可行。在这里我也提醒一下各位读者,有很多网上的判别位数的方法其实都是判断的运行程序的位数,而不是真正操作系统的位数,大家可以通过下图的方法,更换运行时程序的位数来测试!

于是我又开始探索其他思路,在Visual Studio 2010中的环境变量有个方法让我惊喜:System.Environment.Is64BitOperatingSystem,他能够成功的判断操作系统位数,但是失望的是Framework4.0才有这个方法,我们软件目前用的是Framework3.5做平台,只能放弃这个方法了。哎!

在反复尝试中终于又发现一个好方法:Kernel32.dll中的IsWow64Process,具体参考MSDN文档http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx

同时看到了一篇很好的文章http://cfx./SourceControl/changeset/view/39074#,他是先判断IntPtr.Size 是否是8,很明显,只有64位操作系统才会使int的大小为8位,32位操作系统只能是4位。所以如果Size是8则一定是64位操作系统,但这只是充分非必要条件,如果Size不等于8,我们需要调用Kernel32.dll的IsWow64Process方法来判断:

 

代码
/// <summary>
        
/// The function determines whether the current operating system is a 
        
/// 64-bit operating system.
        
/// </summary>
        
/// <returns>
        
/// The function returns true if the operating system is 64-bit; 
        
/// otherwise, it returns false.
        
/// </returns>
        public static bool Is64BitOperatingSystem()
        {
            
if (IntPtr.Size == 8)  // 64-bit programs run only on Win64
            {
                
return true;
            }
            
else  // 32-bit programs run on both 32-bit and 64-bit Windows
            {
                
// Detect whether the current process is a 32-bit process 
                
// running on a 64-bit system.
                bool flag;
                
return ((DoesWin32MethodExist("kernel32.dll""IsWow64Process"&&
                    IsWow64Process(GetCurrentProcess(), 
out flag)) && flag);
            }
        }

        
/// <summary>
        
/// The function determins whether a method exists in the export 
        
/// table of a certain module.
        
/// </summary>
        
/// <param name="moduleName">The name of the module</param>
        
/// <param name="methodName">The name of the method</param>
        
/// <returns>
        
/// The function returns true if the method specified by methodName 
        
/// exists in the export table of the module specified by moduleName.
        
/// </returns>
        static bool DoesWin32MethodExist(string moduleName, string methodName)
        {
            IntPtr moduleHandle 
= GetModuleHandle(moduleName);
            
if (moduleHandle == IntPtr.Zero)
            {
                
return false;
            }
            
return (GetProcAddress(moduleHandle, methodName) != IntPtr.Zero);
        }

        [DllImport(
"kernel32.dll")]
        
static extern IntPtr GetCurrentProcess();

        [DllImport(
"kernel32.dll", CharSet = CharSet.Auto)]
        
static extern IntPtr GetModuleHandle(string moduleName);

        [DllImport(
"kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        
static extern IntPtr GetProcAddress(IntPtr hModule,
            [MarshalAs(UnmanagedType.LPStr)]
string procName);

        [DllImport(
"kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [
return: MarshalAs(UnmanagedType.Bool)]
        
static extern bool IsWow64Process(IntPtr hProcess, out bool wow64Process);

 

 

这样就可正确判断操作系统位数了!其实如果找不到这个方法,我就想只能从程序中执行systeminfo命令,然后接收到所有的输出信息,再进行解析,判断是否是x64Base-PC,但现在不用那么麻烦了O(∩_∩)O~~

(2)接下来恼火的就是那个Windows版本号~~~

从注册表的BuildLabEx可以获得,但是既然泉哥说了最好不要碰注册表,那么还是另想办法吧~~~在.Net的环境变量中找是一个方法:

 OperatingSystem os = System.Environment.OSVersion;

但不巧的是,这个os里面只能找到版本号中的3个参数,6.1.7600,最后一个参数他无能为力了!于是我们就尝试直接用这个参数写入xml,安装IIS,然是它不认,就说参数错误!我再试6.1.7600.0,也不行!再试6.1.7600.*,还是错误!看来不获得最后一个版本号参数是通不过的了!在网上找了又找,终于找到一个突破口!

http://www./os/28318.html 执行

slmgr.vbs -dlv

命令,可以获取完整的版本号信息:

既然它能得到,那么我肯定也可以获得啦!~~~在本地搜索slmgr.vbs,找到它后打开,我对VBScript不是很熟,泉哥帮我看了看,虽然他也不熟,但是找东西的速度真是没话说,两三下就找到了最终调用的方法!它是通过slmgr.ini来获取信息的,里面调用了一个方法

Set colServices = g_objWMIService.ExecQuery("SELECT " & strQuery & " FROM " & ServiceClass)

原来他是调用WMI的函数来实现,WMI调用方法参考的http:///2009/10/c-detect-windows-os-version-%E2%80%93-part-2-wmi/

于是我们的程序就可以这样写了:

 

 

代码
       internal string OSVersion
        {
            
get
            {
                
if (osVersion == null)
                {
                    var ver 
= new System.Management.ManagementObjectSearcher("select Version from SoftwareLicensingService");
                    var items 
= ver.Get();
                    
foreach (var item in items)
                    {
                        
object version = item.GetPropertyValue("Version");
                        osVersion 
= version.ToString();
                    }
                }
                
return osVersion;
            }
        }

 

 

这个OSVersion属性就是我们想要的结果!因此第二种方法也就可以实现了!虽然最终我们的程序用的是第一种方法,但是第二种方法的探索过程中得到的一些结果和经验以后很可能会用到,所以还是拿出来与大家分享^_^

经过我测试,Windows Server 2008(32位)、Windows Server 2008 R2(64位) 和 Windows Vista(32位和64位) 系统的IIS安装和Win7的一样!

最后我们需要做的事情就是自动的判断客户机的操作系统,然后选择合适的方法安装!

问题7:如何判断客户机操作系统类型?

回答:参考了MSDN文档http://msdn.microsoft.com/en-us/library/ms724833(VS.85).aspx

这里详尽的描述了Windows的各个版本,我看见网上有许多只是根据Version number来判断了,看了这幅图你就会发现,其实这种判断并不准确!很多系统都是同一个Version number。

这里我使用Other那一列的方法来判断。这里先声明一下,VER_NT_WORKSTATION和VER_SUITE_WH_SERVER是两个常量,他们的值分别为0x0000001和0x00008000,这个可以在上面给出的网址中找到。Other中还有个OSVERSIONINFOEX参数,这个参数是个结构,通过执行GetVersionEx方法可以为它赋值,我们可以通过判断OSVERSIONINFOEX的各个属性来找到对应的系统名称。所以我们程序的步骤是:先构造一个OSVERSIONINFOEX数组,这个数组的结构是:

这里是C++语法,要将他变为C#语法真是费了一番周折!DWORD-->Int32,WORD-->Int16,BYTE-->Byte,TCHAR-->CHAR[128]。前面的还好说,最后那个Char[]数组比较讨厌了,在C#中结构是值类型,里面如果只是有CHAR[]还可以,但是要是明确的把这个字符数组初始化为Char[128]就不行了,如果是直接在结构里面写Char[] sz=new Char[128]也是不行的,因为结构里不能有实例字段初始值设定项。所以又只有上网找解决办法,果然还是网上牛人多!http://www.cnblogs.com/flier/archive/2004/08/14/33245.html解决代码如下:

 

代码
[StructLayout(LayoutKind.Sequential, Pack = 1)]
    
public struct _OSVERSIONINFOEX
    {
        
public Int32 dwOSVersionInfoSize;
        
public Int32 dwMajorVersion;
        
public Int32 dwMinorVersion;
        
public Int32 dwBuildNumber;
        
public Int32 dwPlatformId;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst 
= 128)]
        
public Char[] szCSDVersion;
        
public Int16 wServicePackMajor;
        
public Int16 wServicePackMinor;
        
public Int16 wSuiteMask;
        
public Byte wProductType;
        
public Byte wReserved;
    }

 

这样就可以了!里面有句UnmanagedType,我想很可能是通过这个方法把这个字符数组变为非托管代码。

然后我们就可以直接用了:

 

代码
 /// <summary>
    
/// 获得OS信息
    
/// </summary>
    class CheckOSInfo
    {
        [DllImport(
"kernel32.dll")]
        
public static extern bool GetVersionEx(ref _OSVERSIONINFOEX osVersionInfo);
        [DllImport(
"user32.dll")]
        
public static extern int GetSystemMetrics(int sm_SERVERR2);
        

        
public static string GetOSName()
        {
            _OSVERSIONINFOEX osVersionInfo 
= new _OSVERSIONINFOEX();
            osVersionInfo.dwOSVersionInfoSize 
= 156;

            
if (GetVersionEx(ref osVersionInfo))
            {
                
switch (osVersionInfo.dwMajorVersion)
                {
                    
case 5:
                        
switch (osVersionInfo.dwMinorVersion)
                        {
                            
case 0:
                                
return "Microsoft Windows 2000";
                            
case 1:
                                
return "Microsoft Windows XP";
                            
case 2:
                                
if ((osVersionInfo.wSuiteMask & 0x00008000!= 0)
                                    
return "Microsoft Windows Home Server";
                                
if (osVersionInfo.wProductType == 1 && CheckOSBitness.Is64BitOperatingSystem())
                                    
return "Microsoft Windows XP";
                                
if(GetSystemMetrics(89)==0)
                                    
return "Microsoft Windows Server 2003";
                                
else
                                    
return "Microsoft Windows Server 2003 R2";
                        }
                        
break;
                    
case 6:
                        
switch (osVersionInfo.dwMinorVersion)
                        {
                            
case 0:
                                
if (osVersionInfo.wProductType == 1)
                                    
return "Microsoft Windows Vista";
                                
else
                                    
return "Microsoft Windows Server 2008";
                            
case 1:
                                
if (osVersionInfo.wProductType == 1)
                                    
return "Microsoft Windows 7";
                                
else
                                    
return "Microsoft Windows Server 2008 R2";
                        }
                        
break;
                }
            }
            
return null;
        }
    }

 

最开始两句是调用一个外部DLL的函数的方法。

这样我们就成功的知道了客户所使用的操作系统类型了!

 

总结一下,Windows 7以前的版本,即包括Windows XP和Windows Server 2003的各个版本的IIS安装,都需要i386文件和配置选项文本文件,其中64位版本的系统还需要AMD64文件,而Windows 7、Windows Vista、Windows Server 2008的IIS安装则只用执行一句命令行就行了。

最终,我们的一键安装IIS程序有一个Source文件夹(里面装有所有版本的i386、amd64以及其他配置文件)和一个我们的程序(获取操作系统类型以及执行一些cmd命令)。

 

最后我们要做的只是把这些所有文件选中,打包压缩成一个自解压文件,自解压文件是exe格式,可直接执行解压。在高级的“自解压选项”中可以设置解压路径,这里我选择的是临时文件夹。另外,还需要设置解压后运行的程序,这里填写我们编写的控制台应用程序的文件名。上两张图:


然后点确定,就压缩好了。把这个exe文件放在任何一台装过.Net Framework3.5 及以上的系统上,直接运行,就可以静默安装IIS了,另外我同时也做了个一键卸载IIS的压缩包。

 

最初我是想把这个压缩包直接发放出来供大家下载的,但是由于种种原因:

1.可能涉及到微软的版权问题,所以i386文件和amd64文件我不能提供给大家,所以只能是大家自己按照我上篇文章的方法获得这些文件。不过我后面提供下载的源代码中的Source文件夹中提供了各个操作系统的bat文件,这些bat文件是我上篇文章中提到过的,用于从光盘中拷贝出所需的i386和amd64文件。读者们只需在执行bat文件时传入2个参数:源文件夹路径和要拷贝到的目标文件夹路径,即可获得该操作系统所需的所有IIS安装文件。

2.如果是提供的下载包含了各个系统的i386文件,最后exe文件差不多有43M左右,文件太大了不方便下载。

3.如果是提供exe文件下载,不免有人会怀疑携带有病毒或木马,不敢下载。

4.等等

鉴于以上原因,不能把最终的IIS一键安装包提供给大家,也请大家理解。

我的源程序中有4个项目:IISClickInstall(一键安装IIS)、IISClickInstall(一键卸载IIS)、IISClickRequiredFiles(生成获取IIS安装文件的bat文件)、IISManager(一些主要方法)。

在提供的下载中,除了源程序文件,我还提供了最后需要生成exe的所有文件,只是Source文件夹中原本应该存放各个系统的i386文件和amd64文件等等,我全部替换为了之前所说的bat文件,大家可以通过bat文件自己获得i386文件和amd64文件放入对应文件夹,然后把这些文件夹按照上面的方法打包成exe自解压文件即可!

到这里,关于IIS的安装内容已经全部结束,其中肯定有许多不足的地方,欢迎大家多多批评指正,共同进步!

这里是下载地址!欢迎大家下载~~~^_^

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多