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

China☆狼群 提交于 2019-12-13 02:39:06

问题


Had to rewrite this post with the actual code since there must be some kind of difference with binary modules.

The full cmdlet is as follows (apologies for length):

In a nutshell, this function basically uses the SMO library and pulls information about a database users from a SQL Server Instance.

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));
        }
    }
}

The cmdlet can be used with a single argument:

Get-DatabaseUserInformation user1

Or I also want to be able to pipe an array to it when dealing with multiple users.

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

If I'm using a single value and that user doesn't exist, then fair enough, it terminates, and it's a case of correcting it and running it again.

But when I'm using it with multiple values, if one of them doesn't exist, it doesn't give any output as all, only the exception.

So the output I get when everything is OK is something like this (with verbose on):

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      {}

What I get when something is wrong: (in this case, user2 has been misspelt.)

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

What I want to happen is something similar to this.

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      {}

回答1:


EDIT: The problem clearly is in the ThrowTerminating error. Read the msdn description here:

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

The remark section states that you should not use this approach if you want to continue processing the pipeline:

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.


You have to write an advanced function like this:

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

The Begin block is where you prepare something, the Process block is where you process each object from the pipeline, that means it is run for each object in pipeline. You clean up the mess in the End block.

Because it is a cmdlet then you can use ErrorAction to control what happens on error:

$values | Get-StringLength -ErrorAction SilentlyContinue

You can even put those errors in a variable using:

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



回答2:


Maybe throw in a try/catch statement, usually works for me. Otherwise, I would check out these two threads:

Continue execution on Exception

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




回答3:


Borrowing code from your earlier revision I make a simple function that accepts pipeline input. For every object passed we attempt to get a user from Active Directory. I was surprised when setting -ErrorAction on the internal cmdlet didn't work. So the simple solution was to set a try/catch block with not catch action.

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

}

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

So the above returns 2 user objects. If I remove the try block I still get the same two objects but I also get an error. Using try suppressed it.


In your update code your are getting a terminating error because that is how you defined it. Try just leaving the catch block empty an see if that works.




回答4:


You need to write a filtering function, rather than a regular function. To do this put the body of the function in a process block:

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

    process
    {
        Write-Output $value.Length
    }
}

This will cause the filter to be called once for each item in the pipeline.




回答5:


Use either -ErrorAction SilentlyContinue or -ErrorAction Continue :

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

Also, don't use ThrowTerminatingError as I think that shuts down the pipeline. Try throwing a regular exception instead.



来源:https://stackoverflow.com/questions/33041405/how-do-i-continue-processing-items-if-one-throws-an-error

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!