前言

无论是直接利用别人博客中的示例还是修改在线脚本代码库——比如PowerShell代码库中发现的脚本,其实能利用、借鉴别人的PowerShell脚本也算一项重要的核心技能。

分析脚本

以下脚本主要用于调用微软IIS Cmdlet——该Cmdlet存在于已安装Web服务角色的Windows Server 2008 R2以及之后版本的操作系统上。

  1. param(
  2. [parameter(Mandatory = $true)]
  3. [string] $Path,
  4. [parameter(Mandatory = $true)]
  5. [string] $Name
  6. )
  7. $System = [Environment]::GetFolderPath("System")
  8. $script:hostsPath = ([System.IO.Path]::Combine($System, "drivers\etc\"))+"hosts"
  9. function New-localWebsite([string] $sitePath, [string] $siteName)
  10. {
  11. try
  12. {
  13. Import-Module WebAdministration
  14. }
  15. catch
  16. {
  17. Write-Host "IIS Powershell module is not installed. Please install it first, by adding the feature"
  18. }
  19. Write-Host "AppPool is created with name: " $siteName
  20. New-WebAppPool -Name $siteName
  21. Set-ItemProperty IIS:\AppPools\$Name managedRuntimeVersion v4.0
  22. Write-Host
  23. if(-not (Test-Path $sitePath))
  24. {
  25. New-Item -ItemType Directory $sitePath
  26. }
  27. $header = "www."+$siteName+".local"
  28. $value = "127.0.0.1 " + $header
  29. New-Website -ApplicationPool $siteName -Name $siteName -Port 80
  30. [CA] -PhysicalPath $sitePath -HostHeader ($header)
  31. Start-Website -Name $siteName
  32. if(-not (HostsFileContainsEntry($header)))
  33. {
  34. AddEntryToHosts -hostEntry $value
  35. }
  36. }
  37. function AddEntryToHosts([string] $hostEntry)
  38. {
  39. try
  40. {
  41. $writer = New-Object System.IO.StreamWriter($hostsPath, $true)
  42. $writer.Write([Environment]::NewLine)
  43. $writer.Write($hostEntry)
  44. $writer.Dispose()
  45. }
  46. catch [System.Exception]
  47. {
  48. Write-Error "An Error occured while writing the hosts file"
  49. }
  50. }
  51. function HostsFileContainsEntry([string] $entry)
  52. {
  53. try
  54. {
  55. $reader = New-Object System.IO.StreamReader($hostsPath + "hosts")
  56. while(-not($reader.EndOfStream))
  57. {
  58. $line = $reader.Readline()
  59. if($line.Contains($entry))
  60. {
  61. return $true
  62. }
  63. }
  64. return $false
  65. }
  66. catch [System.Exception]
  67. {
  68. Write-Error "An Error occured while reading the host file"
  69. }
  70. }

1)第一部分是一个参数块:
image.png
它定义了一个-Path和一个-Name参数,并且这两个参数均为强制参数。当运行该命令时,需要提供这些信息。

2)下一组的命令行看起来更加神秘:
image.png
它看起来并不像有潜在风险的代码——类似GetFolderPath语句并不会导致任何报警。要想知道该代码所实现的功能,需要将该代码在Shell中执行。
image.png
$script:hostsPath代码创建了一个新的变量。这样除了$system变量之外,又有了一个新的变量。这几行命令定义了一个文件夹路径以及文件路径。请记下这几个变量的值,在学习该脚本过程中可以随时参照。
3)该脚本的余下部分包含了3个函数:New-LocalWebsite, AddEntryToHosts和HostsFileContainsEntry。
一个函数类似于一个脚本中的某部分脚本:每个函数都代表已打包的脚本块,该脚本可以被调用。可以看到,尽管在上面的Param()块中并未看到参数,但每个脚本可以定义一个或多个参数。它们采用的方式是仅在函数中才合法的参数定义方法:在函数名称后面的括号中将参数罗列出来(和Param()块一样)。

如果查看该脚本,并不会看到上面定义的任一脚本被调用,因此如果照搬这些脚本,那么脚本根本无法运行。但是在函数New-LocateWebSite中,可以看到调用了函数HostsFileContainsEntry。
image.png
同时,也可以看到,该代码调用了函数AddEntryToHosts。该函数被嵌套在IF语句中。可以在PowerShell中执行Help *IF*获取更多的帮助信息。
image.png
HelpFile通常排在最后,比如这里的about_If

通过阅读该命令对应的结果集,你就可以看到IF语句的工作原理。在上面示例的上下文中,该语句会检查函数HostsFile ContainsEntry的返回值是True还是False;

  • 如果返回False,就会调用函数AddEntryToHosts。

该语句暗示New-LocalWebSite函数才是脚本中“最主要”的函数,或者称之为期望被运行并触发某些变更的函数。HostsFileContainsEntry和AddEntryToHosts函数,看起来就像是函数New-LocalWebSite的功能函数——在需要时才会被调用。所以,此时我们需要关注New-LocalWebSite函数。
image.png
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],请考虑在虚拟机中执行简短的代码片段来查看该片段的功能(使用虚拟机有助于保护你的生产环境)。可以通过在帮助文档中搜寻(使用通配符)这些关键字来查阅更多的信息。