如果出现错误,我该如何继续处理项目?

How do I continue processing items if one throws an error?

必须用实际代码重写此 post,因为二进制模块肯定有某种差异。

完整的cmdlet如下(篇幅抱歉):

简而言之,此函数主要使用 SMO 库并从 SQL 服务器实例中提取有关数据库用户的信息。

namespace Namespace
{
    using Microsoft.SqlServer.Management.Smo;
    using System;
    using System.Collections.Generic;
    using System.Management.Automation;

    using static PrivateFunctions;

    [Cmdlet(VerbsCommon.Get, "DatabaseUserInformation")]
    public class GetDatabaseUserInformationCmdlet : PSCmdlet
    {
        [Parameter(Mandatory = true,
                   HelpMessage = "The user name of the account to retrieve information for.",
                   Position = 0,
                   ValueFromPipeline = true,
                   ValueFromPipelineByPropertyName = true)]
        public string LogonName { get; set; }

        private List<object> userInformation = new List<object>();

        protected override void ProcessRecord()
        {
            string samAccountName = GetSamAccountName(LogonName);
            Login login = null;
            User user = null;

            WriteVerbose($"Getting login for account: {samAccountName}...");
            try
            {
                login = GetLogin(samAccountName);
            }
            catch (InvalidOperationException invalidOperation)
            {
                ThrowTerminatingError(new ErrorRecord(
                                      invalidOperation,
                                      "LoginNotFound",
                                      ErrorCategory.InvalidOperation,
                                      login));

            }

            WriteVerbose($"Getting user for login: {login.Name}...");
            try
            {
                user = GetUser(login);
            }
            catch (InvalidOperationException invalidOperation)
            {
                ThrowTerminatingError(new ErrorRecord(
                                      invalidOperation,
                                      "UserNotFound",
                                      ErrorCategory.InvalidOperation,
                                      user));
            }

            WriteVerbose($"Gathering information for user: {user.Name}");
            var information = new
            {
                LoginName = login.Name,
                UserName = user.Name,
                FullAccess = TestFullAccessOnDatabase(user),
                Roles = user.EnumRoles()
            };

            userInformation.Add(information);

        }

        protected override void EndProcessing()
        {
            WriteVerbose("Writing information to output.");
            userInformation.ForEach(item => WriteObject(item));
        }
    }
}

该 cmdlet 可以与单个参数一起使用:

Get-DatabaseUserInformation user1

或者我也希望在处理多个用户时能够通过管道将数组传递给它。

@('user1', 'user2','user3') | Get-DatabaseUserInformation

如果我使用的是单个值并且该用户不存在,那么很公平,它会终止,并且需要更正它并再次 运行 它。

但是当我将它与多个值一起使用时,如果其中一个不存在,它不会给出任何输出,只有异常。

所以当一切正常时我得到的输出是这样的(详细):

VERBOSE: Getting login for account: DOMAIN\user1...
VERBOSE: Getting user for login: DOMAIN\user1...
VERBOSE: Gathering information for user: dbo
VERBOSE: Getting login for account: DOMAIN\user2...
VERBOSE: Getting user for login: DOMAIN\user2...
VERBOSE: Gathering information for user: user2
VERBOSE: Getting login for account: DOMAIN\user3...
VERBOSE: Getting user for login: DOMAIN\user3...
VERBOSE: Gathering information for user: user3
VERBOSE: Writing information to output.

LoginName           UserName                     FullAccess Roles
---------           --------                     ---------- -----
DOMAIN\user1        dbo                          True       {db_owner}
DOMAIN\user2        user2                        False      {role1}
DOMAIN\user3        user3                        False      {}

出现错误时我得到的结果:(在这种情况下,user2 被拼错了。)

Get-DatabaseUserInformation : A login for the account 'DOMAIN\usr2' does not
exist in the database.
At line:1 char:6
+ $x | Get-DatabaseUserInformation -verbose
+      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-DatabaseUserInformation], InvalidOperationException
    + FullyQualifiedErrorId : LoginNotFound,DatabaseUserManagement.GetDatabase
   UserInformationCmdlet

我想要发生的事情与此类似。

VERBOSE: Getting login for account: DOMAIN\user1...
VERBOSE: Getting user for login: DOMAIN\user1...
VERBOSE: Gathering information for user: dbo
VERBOSE: Getting login for account: DOMAIN\usr2...
Get-DatabaseUserInformation : A login for the account 'DOMAIN\usr2' does not
exist in the database.
At line:1 char:6
+ $x | Get-DatabaseUserInformation -verbose
+      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-DatabaseUserInformation], InvalidOperationException
    + FullyQualifiedErrorId : LoginNotFound,DatabaseUserManagement.GetDatabase
   UserInformationCmdlet
VERBOSE: Getting login for account: DOMAIN\user3...
VERBOSE: Getting user for login: DOMAIN\user3...
VERBOSE: Gathering information for user: user3
VERBOSE: Writing information to output.

LoginName           UserName                     FullAccess Roles
---------           --------                     ---------- -----
DOMAIN\user1        dbo                          True       {db_owner}
DOMAIN\user3        user3                        False      {}

编辑: 问题显然出在 ThrowTerminating 错误中。在此处阅读 msdn 说明:

https://msdn.microsoft.com/en-us/library/system.management.automation.cmdlet.throwterminatingerror(v=vs.85).aspx

remark 部分指出,如果您想继续处理管道,则不应使用此方法:

When a cmdlet encounters a terminating error, call this method rather than simply throwing an exception. Calling this method allows the cmdlet to attach additional error record information that describes the condition that caused the terminating error. When this method is called, the Windows PowerShell runtime catches the error record and then starts shutting down the pipeline. For more information about error reporting and error records, see Windows PowerShell Error Reporting. This method should not be used when errors occur where the cmdlet can continue processing records. To send error reports when nonterminating errors occur, call the WriteError method. For more information about cmdlets, see Windows PowerShell Cmdlets.


你必须像这样写一个高级函数:

Function Get-StringLength()
{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$true)]
        $value
    )
    Begin {}
    Process {
        Write-Output $value.Length
    }
    End {}
}

Begin 块是您准备东西的地方,Process 块是您处理来自管道的每个对象的地方,这意味着它是 运行 管道中的每个对象。您清理 End 块中的混乱。

因为它是一个 cmdlet,所以您可以使用 ErrorAction 来控制发生错误时发生的事情:

$values | Get-StringLength -ErrorAction SilentlyContinue

您甚至可以使用以下方法将这些错误放入变量中:

$values | Get-StringLength -ErrorAction SilentlyContinue -ErrorVariable MyErrors

也许加入 try/catch 语句,通常对我有用。否则,我会检查这两个线程:

Continue execution on Exception

https://social.technet.microsoft.com/forums/scriptcenter/en-US/e9ee76cd-3446-4507-b9e7-60863550fa00/powershell-trycatch-not-continuing-after-error

您需要编写过滤函数,而不是常规函数。为此,将函数体放在 process 块中:

function Get-StringLength()
{
    [CmdletBinding()]
    Param
    (
        [Parameter(ValueFromPipeline=$true)]
        $value
    )

    process
    {
        Write-Output $value.Length
    }
}

这将导致为管道中的每个项目调用一次过滤器。

使用 -ErrorAction SilentlyContinue-ErrorAction Continue :

@('user1', 'user2','user3') | Get-DatabaseUserInformation -ErrorAction Continue

此外,不要使用 ThrowTerminatingError,因为我认为这会关闭管道。尝试抛出常规异常。

借用您早期修订版的代码,我制作了一个接受管道输入的简单函数。对于传递的每个对象,我们尝试从 Active Directory 获取用户。当在内部 cmdlet 上设置 -ErrorAction 不起作用时,我感到很惊讶。所以简单的解决方案是设置一个 try/catch 没有捕获动作的块。

Function Get-AdUsers()
{
    [CmdletBinding()]
    Param(
        [Parameter(ValueFromPipeline=$true)]
        $value
    )
    Process {
        try{Get-ADUser $value}catch{}
    }

}

$values = 'gooduser1','badname','gooduser2'
$values | Get-AdUsers

所以上面returns2个用户对象。如果删除 try 块,我仍然会得到相同的两个对象,但也会收到错误消息。使用 try 抑制它。


在您的更新代码中,您遇到了终止错误,因为您就是这样定义它的。尝试将 catch 块留空,看看是否可行。