利用Powershell在IIS上自动化部署网站
本文主要讲如何通过Powershell在IIS上自动化部署ASP.NET网站,而不涉及Powershell的基本语法,如果没有Powershell基础的同学也可以把本文作为学习Powershell的基石,通过学习本文中的脚本再去查阅具体的语法,可能会达到事半功倍的效果。
一般我们建立网站大致需要以下几个步骤:
1、安装.NET Framework
2、安装了IIS
3、注册、启用ISAPI和CGI限制
4、建立网站
5、设置默认首页、身份验证、设置MIME类型
6、绑定域名或IP地址
7、设置权限
8、设置防火墙入站规则
功能介绍
该功能主要是将站点文件夹、Powershell脚本文件、.NET Framework安装程序、Powershell升级程序放在同一个文件夹下,以管理员身份运行脚本文件,脚本自动安装.NET Framework和升级Powershell并将站点文件拷贝到网站目录下,最终建立一个网站。
接下来我们就讲讲如果通过Powershell实现上面的步骤:
安装.NET Framework
首先检查是否已经安装了.NET Framework,如果没有再安装。目前我知道的有两种方式可以判断是否已经安装了.NET Framework,一种是检查注册表,一种是检查安装路径(有点不靠谱),在本文中我将通过注册表来检查是否已经安装了.NET Framework。.NET Framework的注册表路径在“HKLM:\SOFTWARE\Microsoft\NET Framework Setup\”,所以可以通过以下代码来实现:
test-path "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\"
但是上面的代码只能检查.NET Framework的安装情况,并不知道是安装了哪个版本,所以还需要配合下面的代码:
$version = gci 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP'| sort pschildname -desc | select -fi 1 -exp pschildname
gci是Get-ChildItem的缩写,srot是Sort-Object的缩写,可以通过运行Get-Help Get-ChildItem -Detailed来查看该函数的详细信息,其他函数只要替换掉Get-ChilItem就可以了。具体的代码如下所示:
function CheckFramework { try { $exists = test-path "HKLM:\SOFTWARE\Microsoft\NET Framework Setup\" if($exists -eq $false) { return $false } else { $version = gci 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP'| sort pschildname -desc | select -fi 1 -exp pschildname if($version -ge "v4.0") { return $true } else { return $false } } } catch { Write-Error $_.Exception.Message } }
检查后就是安装.NET Framework,调用安装程序是通过Start-Process函数来实现的,只需找出文件夹下的exe文件,并调用Start-Process函数即可:
Write-Progress "正在安装.NET Framework" "请稍候……" Get-ChildItem $PSScriptRoot -Filter *.exe | %{start -wait $_ -ArgumentList "/quiet"}
Write-Progress "completed" "completed" -Completed
Write-Progress是显示进度条信息,$PSScriptRoot是获取当前脚本所在的路径。-ArgumentList参数表示该安装过程是以静默安装的方式进行,如果没有该参数就会显示具体的安装过程。接下来是升级Powershell到4.0版本,因为后面的脚本是基于4.0来写的。
升级Powershell
在升级之前同样是先检查Powershell的版本,如果已经是4.0版本了就没有必要再重新更新一次了。升级Powershell的方式跟安装.NET Framework的方式是一样的,只是在升级完成时系统会自动重启以完成升级,也可以在安装后不自动重启,只需在-ArgumentList参数里使用"/quiet /norestart"即可,但是本文中的脚本是会自动重启。如果你的脚本不是基于4.0版本的就可以设置为不自动重启了。那么,如何让系统重启后自动执行当前的脚本呢?你可能会想到注册表,没错,本文就是通过写注册表的方式来实现,如果已经是4.0版本的话就可以用另外一种方式来实现了,具体的代码如下:
#Register-ScheduledJob只能在3.0以后使用 #$scriptPath = $MyInvocation.ScriptName #$trigger = New-JobTrigger -AtStartup -RandomDelay 00:01:00 #Register-ScheduledJob -Trigger $trigger -FilePath $scriptPatp -Name UpgradePowershell $registryValue = "{0}\system32\WindowsPowerShell\v1.0\powershell.exe {1}" -f $env:windir,$MyInvocation.ScriptName $registryPath = "HKCU:\Software\Microsoft\Windows\CurrentVersion\RunOnce" $exists = Test-Path $registryPath if($exists -eq $false) { New-Item $registryPath } New-ItemProperty "$registryPath" Upgrade -PropertyType String -Value "$registryValue"
注释的代码就是另外一种实现方式,但是只能在3.0以后的版本中使用。在HKCU下是没有RunOnce这个项的,所以需要先判断该注册表路径是否存在,在HKLM下的话就有RunOnce路径了。RunOnce表示只会执行一次,执行完后该注册信息就会被删除。
安装IIS
在安装调用安装IIS的方法之前需要先使用下面的代码引入ServerManager模块,否则没有办法调用具体的函数:
Import-Module servermanager
添加功能和角色主要用Add-WindowsFeature -name,name参数是功能或角色的名称,如果不知道具体功能和角色的名称可以用Get-WindowsFeature来获取相关角色或功能的名称:
$features = get-windowsfeature web-* foreach($item in $features) { if($item.installed -eq $false) { Write-Host "安装:$item.displayname" $item | add-windowsfeature } }
首先获取以web-开头的所有角色和功能,逐个判断是否已经安装,没有安装的再进行安装。
注册、启用ISAPI和CGI限制
在运行注册命令之前先判断是否已经注册,如果注册了判断是否已经启用。在Powershell注册ISAPI和在命令提示符中注册是差不多的,都是要以管理员身份身份运行。如果是直接运行aspnet_regiis.exe的全路径的话,Powershell和cmd中的命令是一样的,即:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i
如果是先切换到aspnet_regiis.exe的目录下,在cmd下可以直接运行aspnet_regiis.exe -i,在Powershell下则需要运行./aspnet_regiis.exe -i,否则Powershell无法识别aspnet_regiis.exe -i命令。通过下面的脚本获取是否已经注册和启用,并赋值给$isapiConfiguration变量:
$isapiConfiguration = get-webconfiguration "/system.webServer/security/isapiCgiRestriction/add[@path='$isapiPath']/@allowed"
$isapiPath是一个变量,存放isapi的全路径。如果变量$isapiConfiguration等于null的话说明尚未注册isapi,如果变量不等于null,并且$isapiConfiguration.Value等于false的话说明未启用isapi。
#检查系统是否是64bit function Is64Bit { [IntPtr]::Size -eq 8 } #注册或启用ISAPI function RegisterAndEnableIsapi { $is64Bit = Is64Bit $isapiPath="" if($is64Bit) { $isapiPath ="$env:windir\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" } else { $isapiPath ="$env:windir\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll" } $isapiConfiguration = get-webconfiguration "/system.webServer/security/isapiCgiRestriction/add[@path='$isapiPath']/@allowed" if($isapiConfiguration -eq $null) { write-host "IIS尚未注册aspnet_isapi.dll" $tmpPath="" if($is64Bit) { $tmpPath = "$env:windir\Microsoft.NET\Framework64\v4.0.30319\" } else { $tmpPath = "$env:windir\Microsoft.NET\Framework\v4.0.30319\" } set-location $tmpPath .\aspnet_regiis.exe -i $isapiConfiguration = get-webconfiguration "/system.webServer/security/isapiCgiRestriction/add[@path='$isapiPath']/@allowed" } if($isapiConfiguration.Value -eq $false) { write-host "IIS已经注册过aspnet_isapi.dll,但未启用" set-webconfiguration "/system.webServer/security/isapiCgiRestriction/add[@path='$isapiPath']/@allowed" -value true if(Is64Bit) { set-webconfiguration "/system.webServer/security/isapiCgiRestriction/add[@path='$env:windir\Microsoft.NET\Framework\v4.0.30319\aspnet_isapi.dll']/@allowed" -value true } Write-Host "isapi已启用" } else { write-host "IIS已经注册过aspnet_isapi.dll,且已启用" } }
创建应用程序池
在新建应用程序池和新建网站之前需要先引入“WebAdministration”模块,否则会出现下面的错误:
该模块在2.0版本下是没有的,所以要升级到4.0版本。
由于我们手动建立网站的时候会自动创建应用程序池,只需要设置应用程序池的相关属性就可以,但用Powershell脚本新建网站的时候是不会自动创建应用程序池的,所以我们需要先创建好应用程序池,在创建网站的时候将其指向到新建的应用程序池。
set-location iis:\AppPools $existsAppPool = test-path $appPoolName if($existsAppPool -eq $false) { $appPool = new-item $appPoolName #设置标识:LocalService=1;LocalSystem=2;NewworkService=3;ApplicationPoolIdentity=4 $appPool.ProcessModel.IdentityType=4 #设置.NET Framework 版本 $appPool.managedRuntimeVersion="v4.0" #设置托管管道模式:集成=0;经典=1 $appPool.ManagedPipelineMode=0 $appPool.startMode="AlwaysRunning" #设置启用32位应用程序 false=0;true=1 $appPool.enable32BitAppOnWin64=0 $appPool | set-item } else { write-error "应用程序池已经存在" }
创建网站
因为动态压缩功能只要有安装,在新建网站的时候会自动启用,所以有需要启用动态内容压缩功能的话就需要检查该功能是否已经安装。
#安装动态内容压缩功能 function EnableGZip { $check = get-windowsfeature web-dyn-compression if($check.installed -eq $false) { add-windowsfeature web-dyn-compression } }
检查网站目录是否存在,如果不存在就新建一个目录并设置权限,如果要关联的目录不存在的话就会出现下面的错误:
#设置权限 function SetSecurity($name,$path) { $acl= get-acl $path $ar = new-object System.Security.AccessControl.FileSystemAccessRule("$name","ReadAndExecute","ContainerInherit,ObjectInherit","None","Allow") $acl.SetAccessRule($ar) set-acl $acl -path $path } function CheckDirectory($path) { $existsPath=test-path $path if($existsPath -eq $false) { write-host "【$path】目录不存在,新建该目录" new-item -path $path -type directory } #设置network service用户的权限 Write-Progress "正在设置目录权限,请稍候……" SetSecurity "network service" $path SetSecurity "everyone" $path Write-Progress "completed" -Completed }
$name是“组或用户名”,$path是站点路径。
将当前文件夹下的站点文件拷贝到站点目录下,由于拷贝文件可能会比较耗时,所以使用了进度条显示拷贝进度,如果不使用进度条的话就只要两条语句就可以完成:
$siteFilePath = (get-childitem $psscriptroot | ?{$_.psiscontainer})[0].fullname Copy-Item "$siteFilePath\*" $sitePath -Force -Recurse
使用进度条的方式:
#将脚本文件所在目录下的文件夹下的文件全部拷贝到站点目录下 function CopyFiles { $siteFilePath = (get-childitem $psscriptroot | ?{$_.psiscontainer})[0].fullname $files=Get-ChildItem "$siteFilePath\*" $count = $files.Length for($i=0;$i -lt $count;$i++) { $copied = $i+1; Copy-Item $files[$i] $sitePath -Force -Recurse $percentage = $copied/$count $msg = "已拷贝:{0:p0}" -f $percentage Write-Progress -Activity "正在拷贝文件到:【$sitePath】目录" -Status $msg -PercentComplete ($percentage*100) } Write-Progress "拷贝结束" -Completed }
上述准备工作做完之后就是建立网站了
set-location iis:\sites if((test-path $siteName) -eq $true) { write-error "站点已经存在"; } else { #新建站点 new-website $siteName -physicalpath $sitepath #绑定域名 new-webbinding -name $siteName -host $hostname -port 80 -protocol http #获取本机IP $ojbItem = Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter IPEnabled=TRUE -ComputerName . $ipaddress = $ojbItem.IPAddress[0] #绑定IP地址和端口 new-webbinding -name $siteName -ip $ipaddress -port $port -protocol http #设置应用程序池 set-itemproperty $siteName -name applicationpool -value $appPoolName #启用Forms身份验证 $config = get-webconfiguration system.web/authentication $siteName $config.mode="Forms" $config|set-webconfiguration system.web/authentication #启用匿名身份验证 Set-WebConfigurationProperty -Filter system.webServer/security/authentication/anonymousAuthentication -PSPath MACHINE/WEBROOT/APPHOST -Location $siteName -Name Enabled -Value $true }
如果有开启防火墙的话还需要添加入站规则
function AddFirewallRule($name,$tcpPorts,$appName = $null,$serviceName = $null) { try { $fw = New-Object -ComObject hnetcfg.fwpolicy2 $rule = New-Object -ComObject HNetCfg.FWRule $rule.Name = $name if ($appName -ne $null) { $rule.ApplicationName = $appName } if ($serviceName -ne $null) { $rule.serviceName = $serviceName } $rule.Protocol = 6 #NET_FW_IP_PROTOCOL_TCP $rule.LocalPorts = $tcpPorts $rule.Enabled = $true $rule.Grouping = "@firewallapi.dll,-23255" $rule.Profiles = 7 # all $rule.Action = 1 # NET_FW_ACTION_ALLOW $rule.EdgeTraversal = $false $fw.Rules.Add($rule) Write-Host "防火墙入站规则添加成功" } catch { Write-Error $_.Exception.Message } }
创建虚拟目录的比较简单,但是也需要检查虚拟目录的路径是否存在,设置虚拟目录的权限
new-item "$siteName\$name" -type virtualdirectory -physicalpath $path
如果有需要还可以添加MIME类型
#添加扩展名 $mime为哈希表类型 如$mimes = @{".a"="application/stream";".b"="application/stream";".c"="application/stream";} function AddMime($mime) { try { if($mimes -eq $null -or $mimes.count -le 0) { return } foreach($item in $mimes.Keys) { Write-Host "添加MIME类型:$item" $extension = get-webconfigurationproperty //staticcontent -name collection | ?{$_.fileExtension -eq $item} if($extension -ne $null) { write-host "该扩展名已经存在" } else { add-webconfigurationproperty //staticcontent -name collection -value @{fileExtension=$item;mimeType=$mimes[$item]} } } Write-Host "MIME类型添加完成" } catch { Write-Error $_.Exception.Message } }
测试网站
#请求接口 function GetRequest($url) { $request = [System.Net.HttpWebRequest]::Create($url) $response = [System.Net.HttpWebResponse]$request.GetResponse() $code = [System.Int32]$response.StatusCode $response.Close() $response.Dispose() Write-Host $code }
测试网站是通过调用.NET Framework的相关函数来实现的。
以上就是用Powershell脚本自动化部署网站的全部过程,可能还有遗落的功能没有实现。如果你对自动化部署网站有兴趣的话可以自己实现一个Powershell脚本。也请各位大牛多多指教,指出本文中的不足和错误的地方。