前言
无论是直接利用别人博客中的示例还是修改在线脚本代码库——比如PowerShell代码库中发现的脚本,其实能利用、借鉴别人的PowerShell脚本也算一项重要的核心技能。
分析脚本
以下脚本主要用于调用微软IIS Cmdlet——该Cmdlet存在于已安装Web服务角色的Windows Server 2008 R2以及之后版本的操作系统上。
param(
[parameter(Mandatory = $true)]
[string] $Path,
[parameter(Mandatory = $true)]
[string] $Name
)
$System = [Environment]::GetFolderPath("System")
$script:hostsPath = ([System.IO.Path]::Combine($System, "drivers\etc\"))+"hosts"
function New-localWebsite([string] $sitePath, [string] $siteName)
{
try
{
Import-Module WebAdministration
}
catch
{
Write-Host "IIS Powershell module is not installed. Please install it first, by adding the feature"
}
Write-Host "AppPool is created with name: " $siteName
New-WebAppPool -Name $siteName
Set-ItemProperty IIS:\AppPools\$Name managedRuntimeVersion v4.0
Write-Host
if(-not (Test-Path $sitePath))
{
New-Item -ItemType Directory $sitePath
}
$header = "www."+$siteName+".local"
$value = "127.0.0.1 " + $header
New-Website -ApplicationPool $siteName -Name $siteName -Port 80
[CA] -PhysicalPath $sitePath -HostHeader ($header)
Start-Website -Name $siteName
if(-not (HostsFileContainsEntry($header)))
{
AddEntryToHosts -hostEntry $value
}
}
function AddEntryToHosts([string] $hostEntry)
{
try
{
$writer = New-Object System.IO.StreamWriter($hostsPath, $true)
$writer.Write([Environment]::NewLine)
$writer.Write($hostEntry)
$writer.Dispose()
}
catch [System.Exception]
{
Write-Error "An Error occured while writing the hosts file"
}
}
function HostsFileContainsEntry([string] $entry)
{
try
{
$reader = New-Object System.IO.StreamReader($hostsPath + "hosts")
while(-not($reader.EndOfStream))
{
$line = $reader.Readline()
if($line.Contains($entry))
{
return $true
}
}
return $false
}
catch [System.Exception]
{
Write-Error "An Error occured while reading the host file"
}
}
1)第一部分是一个参数块:
它定义了一个-Path和一个-Name参数,并且这两个参数均为强制参数。当运行该命令时,需要提供这些信息。
2)下一组的命令行看起来更加神秘:
它看起来并不像有潜在风险的代码——类似GetFolderPath语句并不会导致任何报警。要想知道该代码所实现的功能,需要将该代码在Shell中执行。$script:hostsPath
代码创建了一个新的变量。这样除了$system变量之外,又有了一个新的变量。这几行命令定义了一个文件夹路径以及文件路径。请记下这几个变量的值,在学习该脚本过程中可以随时参照。
3)该脚本的余下部分包含了3个函数:New-LocalWebsite, AddEntryToHosts和HostsFileContainsEntry。
一个函数类似于一个脚本中的某部分脚本:每个函数都代表已打包的脚本块,该脚本可以被调用。可以看到,尽管在上面的Param()块中并未看到参数,但每个脚本可以定义一个或多个参数。它们采用的方式是仅在函数中才合法的参数定义方法:在函数名称后面的括号中将参数罗列出来(和Param()块一样)。
如果查看该脚本,并不会看到上面定义的任一脚本被调用,因此如果照搬这些脚本,那么脚本根本无法运行。但是在函数New-LocateWebSite
中,可以看到调用了函数HostsFileContainsEntry。
同时,也可以看到,该代码调用了函数AddEntryToHosts
。该函数被嵌套在IF语句中。可以在PowerShell中执行Help *IF*
获取更多的帮助信息。
HelpFile通常排在最后,比如这里的about_If
。
通过阅读该命令对应的结果集,你就可以看到IF语句的工作原理。在上面示例的上下文中,该语句会检查函数HostsFile ContainsEntry的返回值是True还是False;
- 如果返回False,就会调用函数AddEntryToHosts。
该语句暗示New-LocalWebSite函数才是脚本中“最主要”的函数,或者称之为期望被运行并触发某些变更的函数。HostsFileContainsEntry和AddEntryToHosts函数,看起来就像是函数New-LocalWebSite的功能函数——在需要时才会被调用。所以,此时我们需要关注New-LocalWebSite函数。
Tips:
快速查找对应的帮助文档(Help Try)会显示About_Try_Cacth_Finally
帮助文档,其中阐述道:Try部分中的任何命令都有可能产生一个错误信息。如果确实产生了错误信息,那么就会执行Catch部分的命令。
上面的命令可以解释为:该函数会尝试载入WebAdministration模块,如果载入失败,那么会显示一个错误信息。坦白讲,我们认为在发生错误时,应该完全退出该函数,但是在这里并非如此。所以当WebAdministration模块未成功载入时,可以想象,这里会看到更多的错误信息。所以在执行该脚本之前,必须保证WebAdministration模块可用!
4)Write-Host
块主要用作帮助追踪脚本运行进度。下一个命令是New-WebAppPool
。查看帮助文档,发现该命令包含在WebAdministration模块中,该命令的帮助文档阐述了其作用。接下来,Set-ItemProperty命令看起来像是对刚建立的AppPool对象设置某些选项。
5)变量$Header
在创建后被用作将$SiteName转化为一个类似“www.sitename.local”的网址,同时$Value变量用作添加一个IP地址。之后传入多个参数调用New-WebSite命令。
6)最后执行Start-WebSite
命令。在帮助文档中有说明,该命令会使得对应的网站上线并运行。然后调用HostsFileContainsEntry和AddEntryToHosts命令。它们会确保$Value变量中的值对应的站点信息会以“IP地址-名称”的格式被添加到本地Hosts文件中。
逐行检查
在上面,我们采用的是逐行分析该脚本,这也是建议的方式。当你逐行查阅每一行时,完成下述工作。
- 识别其中的变量,并找出其对应的值,之后将它们写在一张纸上。因为大部分情况下,变量都会被传递给某些命令,所以记下每个变量可能的值会帮助你预测每个命令的作用。
- 当你遇到一些新的命令时,请阅读对应的帮助文档,这样可以理解这些命令的功能。针对Get-类型的命令,尝试运行它们——将脚本中变量的值传递给命令的参数——来查看这些命令的输出结果。
- 当你遇到不熟悉的部分时,比如[Environment],请考虑在虚拟机中执行简短的代码片段来查看该片段的功能(使用虚拟机有助于保护你的生产环境)。可以通过在帮助文档中搜寻(使用通配符)这些关键字来查阅更多的信息。