验证 PowerShell 中的函数已成功 运行

Verify a function in PowerShell has run succesfully

我正在编写一个脚本来将现有的 Bit Locker 密钥备份到 Azure AD 中的关联设备,我创建了一个函数来遍历启用了 Bit Locker 的卷并将密钥备份到 Azure,但是我想知道如何检查函数是否已成功完成而没有任何错误。这是我的代码。我已经在函数中添加了一个 try 和 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
}
  • 不幸的是,从 PowerShell 7.2.1 开始,automatic $? variable 在调用 [=82] 后 没有 有意义的值=]written-in-PowerShell function(相对于 binary cmdlet)。 (更直接地说,即使 函数中,$? 也只反映 $falsecatch 块的最开始,正如 Mathias 指出的那样)。

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

    • 您可以通过使用 advanced function or script, but that is quite cumbersome. The same applies to $PSCmdlet.ThrowTerminatingError(), which is the only way to create a statement-terminating error from PowerShell code. (By contrast, the throw 语句中的 $PSCmdlet.WriteError() 来解决此限制,该语句会生成 script 终止错误,即终止整个脚本及其调用者 - 除非 try / catchtrap 语句在调用堆栈的某处捕获错误。

    • 有关详细信息和相关 GitHub 问题的链接,请参阅 this answer

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

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

      • 注意:自选变量名必须传而不用 $;例如,要收集变量 $errs,请使用 -ErrorVariable errs;不要使用 Error / $Error,因为 $Error is the automatic variable 会收集整个 session.

        中发生的所有错误
      • 您可以将其与 common -ErrorAction parameter 结合使用以在最初消除错误 (-ErrorAction SilentlyContinue),以便稍后根据需要发出它们。不要使用 -ErrorAction Stop,因为它会使 -ErrorVariable 变得无用,反而会中止整个脚本。

    • 你可以让错误简单地发生——不需要 try / catch 语句:因为你的代码中没有 throw 语句,你的即使在给定的迭代中发生错误,循环也会继续 运行。

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

    • 顺便说一句:当然,您也可以将函数 output (return) 设为 Boolean ($true$false) 以指示是否发生错误,但这不是设计用于输出 data.

      的函数的选项

这是此方法的概要:

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.

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

的原因

在你的情况下我会做的是创建一个变量或一个文件来捕获错误信息。我向任何名为 'Bob' 的人道歉。这是我经常使用的变量名。

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

$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...

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