嗨,脚本专家 ! 创建一个 Self-Documenting 脚本

The Microsoft Scripting Guys

若要介绍的广泛 brushed 下布宜诺斯艾利斯中的以下天之间的蓝色不可思议深度,必须使用特殊的颜色名称。 cobalt 蓝色、 deep-sea 蓝色、 午夜蓝色、 Holy mackerel 蓝色没有这些甚至开始 portray 覆盖旧的城市的 Canopy 所示的实际颜色。 排 centuries 旧建筑物的一列的宽度人行道是点与小的表。 我坐在空的金属椅子。 通过执行 cinnamon、 cardamom 和 thyme 的提示,perfumed 的非常 lazily drifts。 使用是 outstretched seagulls 浏览与足够技能、 宽和 poise engender 在将大 Kahuna 自己的 envy updrafts。 走提供 F leche 看起来特别 apropos,强要求对从 sights 和 downtown 布宜诺斯艾利斯的声音竞争自己注意。 我在有关使用 VBScript 来帮助管理他们的网络与 Microsoft Premier 客户的城镇。 我从我的照相机包中提取一个折叠式的映射,并处理计划在 Obelisk 我路由从 Calle 佛罗里达 Nueve de Julio 途径。

我发现我在地图上的位置。 我附近购物库在 Galerias Pacifico。 我打开映射通过几次为我尝试使用我的位置定位时间和空间。 有关这一次 three-piece 的深花色一个高,区分查找正在显示,并且代表只的在阳光。 他卷影 dancing playfully 地图上, 使我 glance 设置。 我突出显示为他说,"布宜诺斯艾利斯 dias,se˜nor"。

"布宜诺斯艾利斯 dias 我忠实答复。

"啊,您是一个美国可能?"他答复使用稍有英国的特色。

我正在有些 aback,采用,并在该 affirmative hesitantly 答复。

"这是您第一次在我们公平的城市?"他 inquires。

再一次我正在 reticent 但说"是。

"然后我可能提供我 humble 服务给您。 是您所查找的?"他 politely 询问。

我告诉他,他继续提供说明。 之后,我们聊天几分钟。

"哇 !" 我想要自己。 " self-Documenting 城市 ! 不是需要此映射。 用户是这样友好的过程中只有前人有可帮助您查看地图的时间"。

这是实际几年前。 今天,在北卡罗来纳中我所,外部天气是如此可爱它提醒我布宜诺斯艾利斯我访问的) 和 self-Documenting 的设备可以是如何很好。 如果我们花时间来计划继续,我们可以有执行为我们,类似服务的脚本。 在这篇文章,我们将看到如何开发一个脚本,将收集遵循某一特定模式的其他脚本中的注释。 我们可以修改该模式以满足自己的需要,并且可以扩展之外创建文档的脚本技术。

让我们一下具有仔细位于一些文档的脚本。 图 1 面向您好脚本专家 ! 中 GetSetIEStartPage.ps1 脚本中 文章, 如何更改我的 Internet Explorer 主页?",批注位于然后分配给变量名为的 $ 注释的以下字符串。 下面的字符串标记的开始标记的符号和一个引号 (@") 和一个引号的结束标记和 at 符号 ("@)。 没有我们感兴趣获取的三个注释块。 第一个包含普通脚本标头信息: 标题、 作者、 日期、 关键字和脚本本身中的注释。 在第二个和第三个注释块专门与两个脚本中包含的主要功能。

图 1 GetSetIEStartPage.ps1

Param([switch]$get,[switch]$set,$computer="localhost")
$Comment = @"
NAME: GetSetIEStartPage.ps1
AUTHOR: ed wilson, Microsoft
DATE: 1/5/2009

KEYWORDS: stdregprov, ie, [wmiclass] type accelerator,
Hey Scripting Guy
COMMENTS: This script uses the wmiclass type accelerator
and the stdregprov to get the ie start pages and to set the
ie start pages. Using ie 7 or better you can have multiple
start pages

"@ #end comment

Function Get-ieStartPage()
{
$Comment = @"
FUNCTION: Get-ieStartPage 
Is used to retrieve the current settings for Internet Explorer 7 and greater.
The value of $hkcu is set to a constant value from the SDK that points
to the Hkey_Current_User. Two methods are used to read
from the registry because the start page is single valued and
the second start pages key is multi-valued.

"@ #end comment
 $hkcu = 2147483649
 $key = "Software\Microsoft\Internet Explorer\Main"
 $property = "Start Page"
 $property2 = "Secondary Start Pages"
 $wmi = [wmiclass]"\\$computer\root\default:stdRegProv"
 ($wmi.GetStringValue($hkcu,$key,$property)).sValue
 ($wmi.GetMultiStringValue($hkcu,$key, $property2)).sValue
} #end Get-ieStartPage

Function Set-ieStartPage()
{
$Comment = @"
FUNCTION: Set-ieStartPage 
Allows you to configure one or more home pages for IE 7 and greater. 
The $aryValues and the $Value variables hold the various home pages.
Specify the complete URL ex: "http://www.ScriptingGuys.Com" make sure
to include the quotation marks around each URL. 

"@ #end comment
  $hkcu = 2147483649
  $key = "Software\Microsoft\Internet Explorer\Main"
  $property = "Start Page"
  $property2 = "Secondary Start Pages"
  $value = "https://www.microsoft.com/technet/scriptcenter/default.mspx"
  $aryValues = "https://social.technet.microsoft.com/Forums/en/ITCG/threads/",
  "https://www.microsoft.com/technet/scriptcenter/resources/qanda/all.mspx"
  $wmi = [wmiclass]"\\$computer\root\default:stdRegProv"
  $rtn = $wmi.SetStringValue($hkcu,$key,$property,$value)
  $rtn2 = $wmi.SetMultiStringValue($hkcu,$key,$property2,$aryValues)
  "Setting $property returned $($rtn.returnvalue)"
  "Setting $property2 returned $($rtn2.returnvalue)"
} #end Set-ieStartPage

# *** entry point to script 
if($get) {Get-ieStartpage}
if($set){Set-ieStartPage}

另一个文档从原始文件写入注释,我们需要打开该的原始脚本搜索该的注释,然后编写相应的文本到新文件。 听起来很简单。 噢,是的,我们还需要新的脚本的名称。 让我们称之为 GetCommentsFromScript.ps1。

图 2 中, 所示该 GetCommentsFromScript.ps1 脚本开头来使我们能够提供在运行时信息脚本参数语句。 此处是参数语句。

图 2 GetCommentsFromScript.ps1

Param($Script= $(throw "The path to a script is required."))
Function Get-FileName($Script)
{
 $OutPutPath = [io.path]::GetTempPath()
 Join-Path -path $OutPutPath -child "$(Split-Path $script-leaf).txt"
} #end Get-FileName

Function Remove-OutPutFile($OutPutFile)
{
  if(Test-Path -path $OutPutFile) { Remove-    Item $OutPutFile | Out-Null }
} #end Remove-OutPutFile

Function Get-Comments($Script,$OutPutFile)
{
 Get-Content -path $Script |
 Foreach-Object `
  { 
    If($_ -match '^\$comment\s?=\s?@"')
     { 
      $beginComment = $True 
     } #end if match @"
   If($_ -match '"@')
     { 
      $beginComment = $False
     } #end if match "@
   If($beginComment -AND $_ -notmatch '@"') 
     {
      $_ | Out-File -FilePath $OutPutFile -append
     } # end if beginComment
  } #end Foreach
} #end Get-Comments

Function Get-OutPutFile($OutPutFile)
{
 Notepad $OutPutFile
} #end Get-OutPutFile

# *** Entry point to script ***
$OutPutFile = Get-FileName($script)
Remove-OutPutFile($OutPutFile)
Get-Comments -script $script -outputfile $OutPutFile
Get-OutPutFile($OutPutFile)vw

Param($Script= $(throw "The path to a script   is required."))

使用命令行参数的优点是我们不需要打开此脚本并编辑它提供给脚本我们要复制其批注的路径。 我们通过将默认值分配给 $ 脚本变量进行强制此参数。 默认值使用 throw 命令引发错误,并这意味着该脚本将始终产生错误运行时除非我们提供 –script 参数值。

让我们 digress 为一分钟,看一看如何 图 3 中 DemoThrow.ps1 脚本中使用 throw 语句。 要获取集错误函数中的 throw 语句引发的错误之后,我们首先需要将 $ errorActionPreference 变量设置为 SilentlyContinue。 这防止显示错误,并允许脚本继续。 它是与从 VBScript On Error Resume Next 的设置相同的。 在如果语句用于计算 $ 值变量。 如果匹配遇到 throw 语句,并且引发异常。 要计算错误,我们使用 Get-ErrorDetails 函数。 首先采用的位置是错误计数将具有已按 1 递增因由 throw 语句引发的错误的显示。 我们然后执行第一个错误 (索引值 0 错误是始终最新),并将 Error 对象发送给格式列表 cmdlet。 我们选择所有属性。 调用的信息但是,返回作为对象,并且因此需要直接查询该对象。 我们为此,访问调用对象通过 InvocationInfo 属性 Error 对象。 生成的错误信息如 图 4 所示。

图 3 DemoThrow.ps1

Function Set-Error
{
 $errorActionPreference = "SilentlyContinue"
 "Before the throw statement: $($error.count) errors"
 $value = "bad"
 If ($value -eq "bad") 
   { throw "The value is bad" }
} #end Set-Error

Function Get-ErrorDetails
{
 "After the throw statement: $($error.count) errors"
 "Error details:"
 $error[0] | Format-List -Property * 
 "Invocation information:"
 $error[0].InvocationInfo
} #end Get-ErrorDetails

# *** Entry Point to Script
Set-Error

Get-ErrorDetails

fig04.gif

图 4 The 引发语句用于将引发错误

现在让我们回到我们的主脚本 GetCommentsFromScript.ps1。 我们需要一个函数,将创建新的文本文档将包含的所有评论 gleaned 从脚本的文件名。 为此,我们使用函数关键字,并其后应跟函数的名称。 我们将调用保持一致使用 Windows PowerShell 动词-名词命名约定我们函数 Get-FileName。 获取 FileName 将单一的输入的参数将保留在 $ 脚本变量在函数中的脚本以进行分析路径。 下面是项以获取 FileName 函数:

Function Get-FileName($Script)
{

接下来,我们获取到临时文件夹在本地计算机上的该路径。 执行此,包括使用环境的 Windows PowerShell 驱动器的方法有多种。 但是,我们决定使用静态的 GetTempPath 方法,从 io.path.NET Framework 类。 GetTempPath 方法返回到临时文件夹是我们将用来存储新创建的文本文件的路径。 我们如下所示在 $ OutPutPath 变量中保存临时文件夹路径:

$OutPutPath = [io.path]::GetTempPath()

我们决定脚本名称之后命名新文本文件。 为此,我们需要脚本名称分开脚本存储在该路径。 我们使用拆分路径 cmdlet 执行此 surgery。 –leaf 参数告诉该 cmdlet 返回脚本名称。 如果有需要包含该脚本的目录路径,我们将使用 –parent 参数。 我们将一对括号内的拆分路径命令,因为我们想要首先,进行该操作,然后我们将美元符号将执行的代码并返回脚本的名称创建一个 Sub-expression 括号的前面。 我们可以为扩展名为我们的文本文件 use.ps1 但的可能是混淆因为它是扩展名脚本。 我们因此只需为返回的文件名添加一个的.txt 扩展名,然后放置在一对引号将整个的内容。 现在,我们使用联接路径 cmdlet 我们输出文件创建新的路径。 新路径组成临时文件夹存储在该 $ OutPutPath 变量和创建使用该文件名中的拆分的路径。 我们可以使用字符串操作和连接创建新的文件路径但更可靠使用联接路径和拆分路径来执行这些类型的操作。 下面的代码如下所示:

Join-Path -path $OutPutPath -child "$(Split-Path $script-leaf).txt"
} #end Get-FileName

我们需要确定如何我们处理重复的文件。 我们可能提示用户的表示一个重复的文件存在,使用如下代码:

$Response = Read-Host -Prompt "$OutPutFile already exists. Do you wish to delete it <y / n>?"
if($Response -eq "y")
    { Remove-Item $OutPutFile | Out-Null }
ELSE { "Exiting now." ; exit }

我们可以实现某种类型的命名的算法,通过其使用.old 扩展名重命名,使现有的文件的备份。 如果我们这,代码将类似如下:

if(Test-Path -path "$OutPutFile.old") { Remove-Item-Path "$OutPutFile.old" }
Rename-Item -path $OutPutFile -newname "$(Split-Path $OutPutFile -leaf).old"

或我们可能只是删除现有文件是我选择执行操作。 我们不想执行的操作将删除输出文件函数我们创建通过使用函数关键字,并指定函数的名称中。 使用 $ 输出文件提供给函数,输入,如下所示:

Function Remove-OutPutFile($OutPutFile)
{

要确定如果文件存在,我们使用测试路径 cmdlet,并提供 $ 输出文件变量,对路径参数中包含的字符串。 Test-路径 cmdlet 返回仅真正或在为 False 根据是否找到文件。 这意味着我们可以使用如果语句来评估文件是否存在。 如果找到文件时我们将执行脚本块中的操作。 如果未找到该文件,脚本块不执行。 此处可以看到第一个命令找不到该的文件并返回 False。 在第二个的命令脚本块不执行因为该文件不能是位于:

PS C:\> Test-Path c:\missingfile.txt
False
PS C:\> if(Test-Path c:\missingfile.txt){"found file"}
PS C:\>

在删除输出文件的函数,如果语句用于确定是否存在已引用 $ 输出文件的文件。 如果那样,它会删除使用删除项 cmdlet 中。 删除一个文件时通常返回的信息 pipelined 到在 out-null cmdlet,提供无提示操作。 该代码所示:

if(Test-Path -path $OutPutFile) { Remove-Item $OutPutFile | Out-Null }
} #end Remove-OutPutFile

我们创建输出文件的名称并删除可能可解决任何以前的输出文件后,它将是从脚本中检索批注的时间。 为此,我们创建获取注释函数并向其传递 $ 脚本变量和该 $ 输出文件变量如下所示:

Function Get-Comments($Script,$OutPutFile)
{

现在我们读取脚本使用,Get-Content cmdlet 的我们传递路径给脚本的文本。 当我们使用 Get-Content 读取文件时,文件一次读取一行,并每行传递和管道。 如果我们将结果存储在变量,我们需要一个数组。 我们可以将变量 $ 一个类似于任何其他数组,包括获取通过 Length 属性数组中的元素数和索引直接在该数组,如下所示:

PS C:\fso> $a = Get-Content -Path C:\fso\  GetSetieStartPage.ps1
PS C:\fso> $a.Length
62
PS C:\fso> $a[32]
($wmi.GetMultiStringValue($hkcu,$key,   $property2)).sValue

下面是读取输入的脚本并将其发送和管道的行:

Get-Content -path $Script |

接下来我们需要查找每一行如果它属于注释块内。 要检查管道中的每个行,我们使用 ForEach-Object cmdlet,这样,我们使用在每次一个集合中的单个对象从这类似于 ForEach…Next 语句。 若要继续下一行命令使用返回的刻度线字符 (')。 我们不想为它遇到管道,每个对象执行的操作包含在已标记与大括号 (也称为大括号) 一组脚本块。 此处出现 Get-Content 函数这一部分:

ForEach-Object `
  { 

我们是 ForEach-Object cmdlet 进程块时, 我们需要检查的文本行。 为此,我们使用 If 语句。 $ _ 自动变量用于表示管道上的当前行。 我们使用 –match 运算符来执行与文本行的正则表达式模式匹配。 该 –match 运算符将返回 Boolean 类型的值为 True 或 False 以响应模式开始该模式在 –match 运算符的右侧。 该脚本的这一部分所示:

PS C:\fso> '$Comment = @"' -match '^\$comment\s?=\s?@"'
True

我们使用正则表达式模式包括许多特殊字符:

^ 在开始 —Match

\ —Escape 字符,因此 $ 符号将被视为原义字符和不在正则表达式中使用特殊字符

$ 注释 —Literal 字符

\ s 吗? —Zero 或多个空格字符

= —Literal 字符

\ s 吗? —Zero 或多个空格字符

@ "原义字符

在检查管道上的文本当前行的代码段所示:

If($_ -match '^\$comment\s?=\s?@"')

我们创建一个变量名为的 $ beginComment,用于标记注释块的开头。 如果我们使其在 –match 语句之后,我们已经发现在注释块的开头。 我们将此变量设置设置为 $ True,此处看到:

{ 
  $beginComment = $True
} #end if match @"

接下来,我们检查查看我们是否注释块的末尾。 为此,我们再次使用 –match 运算符。 此时,我们将"@ 字符序列用于关闭下面的字符串。 如果我们找到它我们可以将 $ beginComment 变量设置为 False:

If($_ -match '"@')
     { 
      $beginComment = $False
     } #end if match "@

我们做它过去的前两个如果语句: 第一个标识此字符串中,开头,并第二个找到下面的字符串的末尾。 现在,我们想要获取文本写入我们注释的文件。 要这样做,我们想 $ beginComment 变量设置为 True。 我们还希望确保我们没有看到,在标记的引号 (@") 字符在行上,因为它意味着下面的字符串的末尾。 为使此决定,我们使用复合的如果语句:

If($beginComment -AND $_ -notmatch '@"') 
     {

现在,我们写入输出文件文本。 若要这样做,我们使用 $ _ 自动变量,表示当前行的文本 ; 我们管道以在 out-file cmdlet。 在 out-file cmdlet 接收 $ 输出文件变量包含注释文件路径。 我们使用 –append 参数指定我们要收集注释文件中脚本的所有批注。 如果我们确实不使用附加参数,文本文件将包含最后的注释,因为,默认,在 out-file cmdlet 将覆盖其内容。 然后关闭出所有大的括号。 我认为它最好指示大括号的目的每个右大括号后添加注释。 这样该脚本更容易读取,以及疑难解答和维护更容易:

$_ | Out-File -FilePath $OutPutFile -append
     } # end if beginComment
  } #end ForEach
} #end Get-Comments

现在,我们创建一个函数调用 get-输出文件将打开输出文件为我们要读取的。 因为临时文件夹不易于找到,并且我们需要在输出文件变量 $ 文件路径,有意义使用脚本打开输出文件。 获取输出文件函数接收输出一个单个输入变量名为的 $ 文件,包含我们要打开该注释文件路径。 当我们调用 Get-输出文件函数时, 我们将向其传递 $ 输出文件。 Get-输出文件函数并值将被引用 $ 输出文件变量在函数内部,我们可以传递祝愿任何值。 我们甚至可以直接 (不使用引号字符串) 传递字符串给函数:

Function Get-OutPutFile($OutPutFile)
{
 Notepad $OutPutFile
} #end Get-OutPutFile

Get-OutPutFile -outputfile C:\fso\GetSetieStartPage.ps1

一般情况下,如果您打算收集设置内容要传递给函数,请编写一个的脚本,时最好 encase 相同变量名称将用于内部和外部函数中的数据。 这遵循我们脚本开发的最佳做法之一:"不影响该脚本的工作部分"。 在此示例,当我们调用函数时我们"进行工作"。 若要更改这在将来脚本需要我们编辑字符串值。 通过将字符串放在一个变量,我们可以轻松地编辑变量的值。 我们将实际上,设置提供通过命令行或的内容在另一个函数中完成该变量的值。 尽可能,应避免字符串文字值直接置于该脚本。 后面的代码中,我们使用变量保存到文件,将传递给 Get-输出文件函数的路径:

Function Get-OutPutFile($OutPutFile)
{
 Notepad $OutPutFile
} #end Get-OutPutFile

$OutPutFile = "C:\fso\GetSetieStartPage.ps1"
Get-OutPutFile -outputfile $OutPutFile

完整的 Get-输出文件函数所示:

Function Get-OutPutFile($OutPutFile)
{
 Notepad $OutPutFile
} #end Get-OutPutFile

而不是键入该输出文件的路径的字符串,$ 输出文件变量将接收由 Get-FileName 函数创建的路径。 Get-FileName 函数接收到包含要提取注释的脚本路径。 通过命令行参数有此脚本路径。 当一个函数单输入的参数时可以传递给该函数通过使用括号的一组。 如果在另一方面,函数将使用两个,或多个输入参数,必须使用 –parameter 名称语法:

$OutPutFile = Get-FileName($script)

接下来,我们调用删除输出文件函数 (前面讨论) 并将它传递给该输出文件 $ 输出文件变量中包含的路径:

Remove-OutPutFile($OutPutFile)

我们将确保我们的输出文件的名称时, 我们将调用获取注释函数以从其路径由 $ 脚本变量表示的脚本检索批注。 注释将被写入到 $ 输出文件变量引用的输出文件。 此处出现下面这行代码:

Get-Comments -script $script -outputfile $OutPutFile

当注释所有已撰写到输出文件时,我们最后调用 get-输出文件函数,并将其传递 $ 输出文件变量中包含的路径。 如果不想要打开注释文件,可以轻松地在超出您的脚本注释行或只是从您的脚本中删除它和获取输出文件函数本身。 如果您不想保存之前查看每个文件,请在就地将代码的行:

Get-OutPutFile($OutPutFile)

当 GetCommentsFromScript.ps1 脚本运行时,没有确认消息将会显示在屏幕上。 只有确认脚本起作用的是新创建的文本文件显示在记事本,中,如 图 5 所示的存在。

fig05.gif

图 5 在记事本中显示新的文本文件

GetCommentsFromScript.ps1 脚本可以轻松地适应您自己的方式编写脚本或甚至对于从基于文本的日志文件中收集其他类型的文档。 您只需修改用于将标记开头的正则表达式模式并且的文本部分的末尾您所感兴趣收集。 希望您喜欢该的脚本并邀请您加入我们在脚本中心,我们在其中发布您是脚本专家的新好 ! 每个工作日的文章。

Ed Wilson ,一个已知的脚本专家是包括 Windows PowerShell 脚本编写指南 (Microsoft Press 2008) 和 Microsoft Windows PowerShell Step by Step (Microsoft Press 2007) 的八个书籍的作者。 Ed 包含超过 20 个包括 Microsoft 认证系统工程 (MCSE) 和认证信息系统安全专业人员 (CISSP) 行业认证。 在他空闲的时间他受 woodworking、 underwater 摄影和 scuba 所在。 和工作。

Craig Liebendorfer 是 wordsmith 和 longtime 的 Microsoft Web 编辑器。 Craig 仍不能相信还有支付他使用单词每天的作业。 他最喜欢的事情之一是 irreverent 幽默,因此他应适合在此处的右侧。 他认为他的最大 accomplishment 为他你的女儿生命周期中。