大家好,今天接着上次的话题,我们继续讨论如何使用高级函数实现Windows Update Agent API的PowerShell模块。今天要为大家介绍的是一个比较复杂的函数Set-WUAutoUpdateSetting。首先我先给出示例代码和效果截图,随后我将分析这个函数运用到了哪些PowerShell V2高级函数的功能。代码稍微有点长,还请部分朋友习惯下,因为这是从PowerShell User到PowerShell Scripter的必由之路。

Function Set-WUAutoUpdateSetting

{

    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]

    Param

    (

       [Parameter(Mandatory=$false,Position=0)][Boolean]$FeaturedUpdatesEnabled,

       [Parameter(Mandatory=$false,Position=1)][Boolean]$IncludeRecommendedUpdates,

       [Parameter(Mandatory=$false,Position=2)][Boolean]$NonAdministratorsElevated,

       [Parameter(Mandatory=$false,Position=3)][ValidateRange(0,4)][INT]$NotificationLevel

    )

    DynamicParam

    {

       if ($NotificationLevel -eq 4) {

           #Define dynamic parameter ScheduledInstallationDay

            $paramDictionary = New-Object System.Management.Automation.RuntimeDefinedParameterDictionary

           $validateRange1 = New-Object System.Management.Automation.ValidateRangeAttribute(0,7)

            $paraAttributes1 = New-Object System.Management.Automation.ParameterAttribute

           $paraAttributes1.Mandatory = $false

           $paraAttributes1.Position = 4

           $attributeCollection1 = new-object -Type System.Collections.ObjectModel.Collection``1[System.Attribute]

           $attributeCollection1.Add($paraAttributes1)         

            $dyParam1 = New-Object System.Management.Automation.RuntimeDefinedParameter("ScheduledInstallationDay",[INT],$attributeCollection1)

           $dyParam1.Attributes.Add($attrCol1)

           $dyParam1.Attributes.Add($validateRange1)

            $paramDictionary.Add("ScheduledInstallationDay",$dyParam1)

          

            #Define dynamic parameter ScheduledInstallationTime

           $validateRange2 = New-Object System.Management.Automation.ValidateRangeAttribute(0,23)

            $paraAttributes2 = New-Object System.Management.Automation.ParameterAttribute

           $paraAttributes2.Mandatory = $false

           $paraAttributes2.Position = 5

           $attributeCollection2 = new-object -Type System.Collections.ObjectModel.Collection``1[System.Attribute]

           $attributeCollection2.Add($paraAttributes2)         

            $dyParam2 = New-Object System.Management.Automation.RuntimeDefinedParameter("ScheduledInstallationTime",[INT],$attributeCollection2)

           $dyParam2.Attributes.Add($attrCol2)

           $dyParam2.Attributes.Add($validateRange2)

            $paramDictionary.Add("ScheduledInstallationTime",$dyParam2)

          

           return $paramDictionary

       }

    }

    Process

    {

       $params = $pscmdlet.MyInvocation.BoundParameters

       foreach ($commonParameter in $commonParameters)  {

           if ($params.ContainsKey($commonParameter)) {

              $params.Remove($commonParameter) | Out-Null

           }

       }

       if (-not $(Test-Privilege)) {

           $customError = New-CustomErrorRecord `

           -ExceptionString "Please run Set-WUAutoUpdateSetting under administrator's privilege." `

           -ErrorID "Error" -ErrorCategory "NotSpecified" `

           -TargetObject $pscmdlet    

           $pscmdlet.ThrowTerminatingError($customError)       

       }

       if ($params.Count -eq 0) {

           $customError = New-CustomErrorRecord `

           -ExceptionString "Set-WUAutoUpdateSetting failed. Please specify at least one parameter." `

           -ErrorID "Error" -ErrorCategory "NotSpecified" `

           -TargetObject $pscmdlet    

           $pscmdlet.ThrowTerminatingError($customError)

       } else {

           $serviceEnabled = (Get-WUAutoUpdateSetting).ServiceEnabled

           if ($serviceEnabled) {

              foreach ($param in $params.GetEnumerator())   {

                  $propertyName = $param.Key

                  $propertyValue = $param.Value

                  $muAU.Settings.$propertyName = $propertyValue

                  Try

                  {

                     $returnWithError = $false

                     if ($pscmdlet.ShouldProcess($propertyName))   {

                         $muAU.Settings.Save()

                     }

                  }

                  Catch

                  {

                     $returnWithError = $true

                  }

                  Finally

                  {

                     if (-not $returnWithError) {

                         switch ($propertyName) {

                            "NotificationLevel" {

                                $propertyValue = $HTNotificationLevel[$propertyValue]

                                $pscmdlet.WriteVerbose("Succeeded to change the setting $propertyName to $propertyValue.")

                            }

                            "ScheduledInstallationDay" {

                                $propertyValue = $HTScheduledInstallationDay[$propertyValue]

                                $pscmdlet.WriteVerbose("Succeeded to change the setting $propertyName to $propertyValue.")

                            }

                            "ScheduledInstallationTime" {

                                $propertyValue = $propertyValue.ToString() + ":00"

                                $pscmdlet.WriteVerbose("Succeeded to change the setting $propertyName to $propertyValue.")

                            }                          

                            Default {

                                $pscmdlet.WriteVerbose("Succeeded to change the setting $propertyName to $propertyValue.")

                            }

                         }

                     } else {

                         $customError = New-CustomErrorRecord `

                         -ExceptionString "Failed to change the setting $propertyName to $propertyValue." `

                         -ErrorID "UnknownError" -ErrorCategory "NotSpecified" `

                         -TargetObject $pscmdlet                      

                         $pscmdlet.WriteError($customError)

                     }     

                  }

              }

           } else {

              $customError = New-CustomErrorRecord `

              -ExceptionString "Windows update service is disabled." `

              -ErrorID "Error" -ErrorCategory "NotSpecified" `

              -TargetObject $pscmdlet    

              $pscmdlet.ThrowTerminatingError($customError)       

           }

       }

    }

}

代码的执行效果如下:

image

首先大家从命令的执行结果中可以发现,该高级函数利用了上次我们讲的$pscmdlet的WriteVerbose方法实现了详细输出效果。这次我们还要来看一下如何在高级函数中实现-WhatIf参数。

要实现-Whatif其实很简单,首先我们需要在高级函数最开始添加SupportsShouldProcess=$true,然后在需要进行Whatif处理的参数的具体代码包含在if ($pscmdlet.ShouldProcess($Param1)) {code}结构中。参数的执行效果也是在大家预期之内的:

image

相信大家非常希望自己写出来的高级函数能接近原生的cmdlet,那么就请各位多多使用以上技巧。

接下来根据代码顺序,我们再来回顾下高级函数中的一些非常有用的特性。首先是[CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")]。CmdletBinding用于标识出这个函数为高级函数,SupportsShouldProcess之前的介绍已经说了,启用该设置后,高级函数将支持一些常规参数。而由于Set-WUAutoUpdateSetting是一个涉及到修改的函数,因此设置ConfirmImpact的值为High,用于提示用户。

接下来是Param(code)定义该函数的输入参数。这里的技巧相比大家已经比较熟悉了,Mandatory用于指定该参数是否是必要参数,Position指定参数的顺序,[Boolean]指定参数的类型。而[ValidateRange(0,4)]则用于指定参数的合法输入范围,用于进行输入判断。以下是NotificationLevel的相关枚举信息:

0

aunlNotConfigured

1

aunlDisabled

2

aunlNotifyBeforeDownload

3

aunlNotifyBeforeInstallation

4

aunlScheduledInstallation

接下来是定义动态参数的部分。从代码中大家可以看到我一共定义了两个参数ScheduledInstallationDay和ScheduledInstallationTime。而动态参数的生成条件是当NotificationLevel的值为4时才有效,这也很好理解。因为只有设置为4时具体时间才有意义。动态参数的创建过程,我这里不在详细叙述了,因为这些示例代码在PowerShell的帮助文档里就有。大家只需根据需要进行修改就行。总结起来就是之前我们通过快捷方式建立起来的各种参数属性,在动态参数这里,需要我们通过创建.NET对象来实现。只要大家习惯这个过程后应该是不太难上手的。

在完成Set-WUAutoUpdateSetting的参数部分定义之后,在具体修改代码之前,我们还是要对输入参数进行一定处理,避免Set-WUAutoUpdateSetting异常终止。首先将输入的参数存到$params变量中,用户输入的变量一开始是存放在$pscmdlet.MyInvocation.BoundParameters对象中的,这里将它存在我们自定义的变量中,便于后续的操作。随后使用foreach循环将$params中的通用参数移除。然后进行权限测试,当用户没有“以管理员身份”运行PowerShell时,将直接退出Set-WUAutoUpdateSetting。接下来是判断用户有没有输入除常规参数之外的参数,如果没有也直接退出Set-WUAutoUpdateSetting。关于这两个判断的效果,可以看一下截图:

image

在处理完这些之后,我们接下来要做的就是实现真正的修改。首先还是一个防止错误产生的判断,只有当服务启用后我们的修改才是有意义的。当确定服务是启用状态后,我们就可以用foreach对用户输入的参数进行处理,然后将用户的输入进行保存。

foreach ($param in $params.GetEnumerator())     {

         $propertyName = $param.Key

         $propertyValue = $param.Value

         $muAU.Settings.$propertyName = $propertyValue

这里的$params的对象类型是哈希表,因此循环时需要注意下,相关介绍可以看下这里。然后$muAU对象是用New-Object -ComObject Microsoft.Update.AutoUpdate创建的。这里需要注意的是以上代码仅仅是将用户指定的值赋值给相关对象的属性,但是这个修改并没有提交,也就是没有完成修改。如果需要完整修改则需要使用$muAU.Settings.Save()提交修改。然后提交修改这句代码,可能会因为一些未知的错误而导致执行失败,因此我们需要使用Try/Catch/Finally来捕获可能的错误。这里我在Try块中使用了if ($pscmdlet.ShouldProcess($propertyName)) {code}。主要目的是实现-Confirm这个常规参数。而在Finally块中这实现了-Verbose参数。因此两个参数的一起使用的效果如下:

image

接下来,我们来测试错误处理代码到底有没有生效。这里我采取的方法是将HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update位置的权限移除,命令执行后得到了以下结果:

image

大家可以注意到,我们的错误处理代码是生效的,同时由于我是将整个注册表键的权限移除,因此这里产生了三个错误。而且大家需要注意到一点,这里产生的错误是非终止性错误,而不是用throw产生的错误。产生这个自定义错误的代码如下:

Function New-CustomErrorRecord

{

    [CmdletBinding(ConfirmImpact="none")]

    Param

    (

       [Parameter(Mandatory=$true,Position=0)][String]$ExceptionString,

       [Parameter(Mandatory=$true,Position=1)][String]$ErrorID,

       [Parameter(Mandatory=$true,Position=2)][String]$ErrorCategory,

       [Parameter(Mandatory=$true,Position=3)][PSObject]$TargetObject

    )

    Process

    {

       $exception = New-Object System.Management.Automation.RuntimeException($ExceptionString)

       $category = [System.Management.Automation.ErrorCategory]::$ErrorCategory

       $customError = New-Object System.Management.Automation.ErrorRecord($exception,$ErrorID,$category,$TargetObject)

       return $customError

    }

}

还有一点需要大家注意,由于以上代码是从PowerShell模块中单独提取的,目的只是给大家介绍下高级函数是如何编写的。以上代码需要在Windows Update Agent PowerShell Module中才能正常工作,而这个Module将于下次放出。

好了,本次所要介绍的内容就到这里结束了,下次将是本系列介绍的最后一篇,敬请期待。

已发表 2011年9月12日 20:08 作者 ghjconan

归档在:PowerShell V2介绍