【问题标题】:Verify a function in PowerShell has run succesfully验证 PowerShell 中的函数是否已成功运行
【发布时间】:2022-01-05 17:20:20
【问题描述】:

我正在编写一个脚本来将现有的位锁密钥备份到 Azure AD 中的关联设备,我创建了一个函数,该函数通过启用位锁的卷并将密钥备份到 Azure 但是想知道如何我可以检查该功能是否已成功完成,没有任何错误。这是我的代码。我已经在函数中添加了一个 try and catch 以捕获函数本身的任何错误但是我如何检查函数是否已成功完成 - 目前我有一个 IF 语句检查最后一个命令是否已运行“$? - 这是是否正确或如何验证?

    function Invoke-BackupBDEKeys {

        ##Get all current Bit Locker volumes - this will ensure keys are backed up for devices which may have additional data drives
        $BitLockerVolumes = Get-BitLockerVolume | select-object MountPoint
        foreach ($BDEMountPoint in $BitLockerVolumes.mountpoint) {

            try {
            #Get key protectors for each of the BDE mount points on the device
            $BDEKeyProtector = Get-BitLockerVolume -MountPoint $BDEMountPoint | select-object -ExpandProperty keyprotector
            #Get the Recovery Password protector - this will be what is backed up to AAD and used to recover access to the drive if needed
            $KeyId = $BDEKeyProtector | Where-Object {$_.KeyProtectorType -eq 'RecoveryPassword'}
            #Backup the recovery password to the device in AAD
            BackupToAAD-BitLockerKeyProtector -MountPoint $BDEMountPoint -KeyProtectorId $KeyId.KeyProtectorId
            }
             catch {
                 Write-Host "An error has occured" $Error[0] 
            }
        }
    }     

#Run function
    Invoke-BackupBDEKeys

if ($? -eq $true) {

    $ErrorActionPreference = "Continue"
    #No errors ocurred running the last command - reg key can be set as keys have been backed up succesfully
    $RegKeyPath = 'custom path'
    $Name = 'custom name'
    New-ItemProperty -Path $RegKeyPath -Name $Name -Value 1 -Force
    Exit
}
 else {
    Write-Host "The backup of BDE keys were not succesful"
    #Exit
}

【问题讨论】:

  • BackupToAAD-BitLockerKeyProtector 可能会引发异常,$? 仍会返回 $true - 您的 catch 块隐藏了这一事实。如果您想在调用堆栈的上层处理错误,则需要重新抛出错误(或抛出新错误)
  • 为什么不在同一个函数中包含所有代码,if 语句的内容可能在您的 try 语句和 中的 else catch 语句。

标签: function powershell bitlocker


【解决方案1】:
  • 不幸的是,从 PowerShell 7.2.1 开始,automatic $? variable 在调用 written-in-PowerShell 函数后没有有意义的值 (相对于 二进制 cmdlet)。 (更直接地说,即使在函数内部,$? 也只反映 $false 在 catch 块的最开始处,正如 Mathias 所指出的)。

    • 如果 PowerShell 函数具有与二进制 cmdlet 相同的功能,则发出至少 一个(非脚本终止)错误,例如 Write-Error,将在调用者的范围内设置 $?到$false,但目前并非如此。

    • 您可以通过使用advanced function 或脚本中的$PSCmdlet.WriteError() 来解决此限制,但这非常麻烦。这同样适用于$PSCmdlet.ThrowTerminatingError(),这是从 PowerShell 代码创建 statement 终止错误的唯一方法。 (相比之下,throw 语句会生成 script 终止错误,即终止整个脚本及其调用者 - 除非 try / catch 或 trap 语句在某处捕获错误向上调用堆栈)。

    • 有关更多信息和相关 GitHub 问题的链接,请参阅 this answer。

  • 作为一种解决方法,我建议:

    • 使您的函数成为一个高级函数,以便支持common -ErrorVariable parameter - 它允许您在自选变量中收集函数发出的所有非终止错误。

      • 注意:自选变量名必须传递没有$;例如,要在变量 $errs 中收集,请使用 -ErrorVariable errs;不要使用Error / $Error,因为$Error is the automatic variable 会收集整个会话中发生的所有错误。

      • 您可以将其与 common -ErrorAction parameter 结合使用,以最初使错误静音 (-ErrorAction SilentlyContinue),以便您以后可以按需发出它们。不要使用-ErrorAction Stop,因为它会使-ErrorVariable 变得无用,反而会中止整个脚本。

    • 您可以让错误简单地发生 - 不需要 try / catch 语句:由于您的代码中没有 throw 语句,即使在给定的迭代中发生错误,您的循环也将继续运行.

      • 注意:虽然可以使用try / catch 捕获循环内的终止错误,然后使用@987654358 将它们中继作为非终止错误@ 在catch 块中,您将在传递给-ErrorVariable 的变量中遇到每个此类错误两次。 (如果您不转发,错误仍会被收集,但不会打印。)
    • 调用后,检查是否收集到任何错误,以确定是否至少有一个密钥没有备份成功。

    • 顺便说一句:当然,你也可以让你的函数输出(返回)一个布尔值($true 或$false)来指示是否发生了错误,但这不会是用于输出数据的函数的一个选项。

以下是这种方法的概要:

function Invoke-BackupBDEKeys {
  # Make the function an *advanced* function, to enable
  # support for -ErrorVariable (and -ErrorAction)
  [CmdletBinding()]
  param()

  # ...
  foreach ($BDEMountPoint in $BitLockerVolumes.mountpoint) {

      # ... Statements that may cause errors.
      # If you need to short-circuit a loop iteration immediately
      # after an error occurred, check each statement's return value; e.g.:
      #      if (-not $BDEKeyProtector) { continue }
  }
}     

# Call the function and collect any
# non-terminating errors in variable $errs.
# IMPORTANT: Pass the variable name *without the $*.
Invoke-BackupBDEKeys -ErrorAction SilentlyContinue -ErrorVariable errs

# If $errs is an empty collection, no errors occurred.
if (-not $errs) {

  "No errors occurred"
  # ... 
}
else {
  "At least one error occurred during the backup of BDE keys:`n$errs"
  # ...
}

这是一个最小的示例,它使用脚本块代替函数:

& {
  [CmdletBinding()] param() Get-Item NoSuchFile 
} -ErrorVariable errs -ErrorAction SilentlyContinue
"Errors collected:`n$errs"

输出:

Errors collected:
Cannot find path 'C:\Users\jdoe\NoSuchFile' because it does not exist.

【讨论】:

  • 谢谢。我已经能够遵循这个并进行了一些操作以适应我的理解,但是当在 catch 中包含 $Error[0] 时,我在使用 -Error Variable $ 运行函数时无法得到这个错误 - 这是空白的,不包含 catch 块中的错误 - 见下文: Invoke-BackupBDEKeys -ErrorAction SilentlyContinue -ErrorVariable $Errors $Erros 变量不包含函数内的 catch 块中的错误?请指教
  • catch { # 将捕获的终止错误转换为非终止错误 # 并继续循环。 $Errors = Write-Error -Message "Error has occurred" #Write-Error -Message "An error has occurred backing the BDY keys to AAD" } 当运行函数 Invoke-BackupBDEKeys -ErrorAction Stop -ErrorVariable $Errors 时,错误是未在 $Erros 变量中返回
  • @Russeller,您必须将不带$ 的变量名传递给-ErrorVariable。不要使用-ErrorAction Stop - 它会整体中止您的脚本并使-ErrorVariable 无用。请查看我的更新 - 我已将方法更改为根本不使用 try / catch。
【解决方案2】:

如其他地方所述,您正在使用的 try/catch 是阻止错误条件中继的原因。这是设计使然,也是使用 try/catch 的有意原因。

在您的情况下,我会做的是创建一个变量或一个文件来捕获错误信息。我向任何名为“鲍勃”的人道歉。这是我经常用于快速处理的变量名。

这是一个有效的基本示例:

$bob = (1,2,"blue",4,"notit",7)

$bobout = @{}                               #create a hashtable for errors

foreach ($tempbob in $bob) {
   $tempbob
   try {
      $tempbob - 2                          #this will fail for a string
   } catch {
      $bobout.Add($tempbob,"not a number")  #store a key/value pair (current,msg)
   }
}

$bobout                                     #output the errors

在这里,我们创建了一个数组,只是为了使用 foreach。把它想象成你的 $BDEMountPoint 变量。

遍历每一个,做你想做的事。在 }catch{} 中,您只想在失败时说“不是数字”。这是它的输出:

-1
0
2
5

Name                           Value
----                           -----
notit                          not a number
blue                           not a number

所有的数字都有效(你可以明显地抑制输出,这只是为了演示)。 更重要的是,我们存储了失败时的自定义文本。

现在,您可能需要更多信息错误。您可以像这样抓住实际发生的错误:

$bob = (1,2,"blue",4,"notit",7)

$bobout = @{}                               #create a hashtable for errors

foreach ($tempbob in $bob) {
   $tempbob
   try {
      $tempbob - 2                          #this will fail for a string
   } catch {
      $bobout.Add($tempbob,$PSItem)         #store a key/value pair (current,error)
   }
}

$bobout

这里我们使用了被检查的当前变量$PSItem,通常也被称为$_。

-1
0
2
5

Name                           Value
----                           -----
notit                          Cannot convert value "notit" to type "System.Int32". Error: "Input string was not in ...
blue                           Cannot convert value "blue" to type "System.Int32". Error: "Input string was not in a...

您还可以解析实际错误并根据它采取措施或存储自定义消息。但这超出了这个答案的范围。 :)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-16
    • 2018-12-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-21
    相关资源
    最近更新 更多