4

Question

Is there a simple way to add exception handling to a proxy cmd's process functionality?

As you can see below, I did find a way to do this, but I suspect there's a far cleaner solution.

Full Story

I want to create a proxy function for Get-ADUser which resolves the current issue with exceptions being thrown where an account cannot by found by identity. With the standard function the -ErrorAction parameter has no effect; my plan was simply to prevent such exceptions from being thrown.

I created a Proxy for Get-ADUser via the following: [System.Management.Automation.ProxyCommand]::Create((New-Object System.Management.Automation.CommandMetaData (Get-Command Get-ADUser))).

I then pasted the result into a function Get-AdUserNullIfNotExist { <# output of the above pasted here #> }.

If I alter the $ScriptCmd as below I get Exception calling "GetSteppablePipeliine ... Only a script block that contains exactly one pipeline or command can be converted. The same is try if I attempt to use trap instead of try-catch.

#$scriptCmd = {& $wrappedCmd @PSBoundParameters} 
$scriptCmd = {
    try {
        & $wrappedCmd @PSBoundParameters
    } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
        write-output $null
    } catch {
        throw
    }
} 

If instead I try to add this logic in the process block (as below) I get: Object reference not set to an instance of an object from $steppablePipeline.End(); presumably because the output from the proxy's pipeline isn't available to the $steppablePipeline's stream.

process
{
    try {
        $steppablePipeline.Process($_)
    } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
        $null #I've tried both with and without this line
    } catch {
        throw
    }
}

In the end I got this to work by copying the param block into a second $wrappedCmd variable and calling one from the other... but this feels horredously hacky.

$wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ADUser', [System.Management.Automation.CommandTypes]::Cmdlet)
$wrappedCmd2 = {
    [CmdletBinding(DefaultParameterSetName='Filter')]
    param(
        [Parameter(ParameterSetName='Filter', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Filter}
        ,
        [Parameter(ParameterSetName='LdapFilter', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${LDAPFilter}
        ,
        [Alias('Property')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        ${Properties}
        ,
        [Parameter(ParameterSetName='Filter')]
        [Parameter(ParameterSetName='LdapFilter')]
        [ValidateRange(0, 2147483647)]
        [ValidateNotNullOrEmpty()]
        [int]
        ${ResultPageSize}
        ,
        [Parameter(ParameterSetName='LdapFilter')]
        [Parameter(ParameterSetName='Filter')]
        [System.Nullable[int]]
        ${ResultSetSize}
        ,
        [Parameter(ParameterSetName='LdapFilter')]
        [Parameter(ParameterSetName='Filter')]
        [ValidateNotNull()]
        [string]
        ${SearchBase}
        ,
        [Parameter(ParameterSetName='Filter')]
        [Parameter(ParameterSetName='LdapFilter')]
        [ValidateNotNullOrEmpty()]
        [Microsoft.ActiveDirectory.Management.ADSearchScope]
        ${SearchScope}
        ,
        [Parameter(ParameterSetName='Identity', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        [Microsoft.ActiveDirectory.Management.ADUser]
        ${Identity}
        ,
        [Parameter(ParameterSetName='Identity')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Partition}
        ,
        [ValidateNotNullOrEmpty()]
        [string]
        ${Server}
        ,
        [ValidateNotNullOrEmpty()]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential}
        ,
        [Microsoft.ActiveDirectory.Management.ADAuthType]
        ${AuthType}
    )
    try {
        & $wrappedCmd @PSBoundParameters 
    } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
        $null
    }
} 
$scriptCmd = {& $wrappedCmd2 @PSBoundParameters} 

Full Code for Proxy Function

function Get-AdUserNullIfNotExist {
    [CmdletBinding(DefaultParameterSetName='Filter')]
    param(
        [Parameter(ParameterSetName='Filter', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Filter}
        ,
        [Parameter(ParameterSetName='LdapFilter', Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]
        ${LDAPFilter}
        ,
        [Alias('Property')]
        [ValidateNotNullOrEmpty()]
        [string[]]
        ${Properties}
        ,
        [Parameter(ParameterSetName='Filter')]
        [Parameter(ParameterSetName='LdapFilter')]
        [ValidateRange(0, 2147483647)]
        [ValidateNotNullOrEmpty()]
        [int]
        ${ResultPageSize}
        ,
        [Parameter(ParameterSetName='LdapFilter')]
        [Parameter(ParameterSetName='Filter')]
        [System.Nullable[int]]
        ${ResultSetSize}
        ,
        [Parameter(ParameterSetName='LdapFilter')]
        [Parameter(ParameterSetName='Filter')]
        [ValidateNotNull()]
        [string]
        ${SearchBase}
        ,
        [Parameter(ParameterSetName='Filter')]
        [Parameter(ParameterSetName='LdapFilter')]
        [ValidateNotNullOrEmpty()]
        [Microsoft.ActiveDirectory.Management.ADSearchScope]
        ${SearchScope}
        ,
        [Parameter(ParameterSetName='Identity', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
        [ValidateNotNull()]
        [Microsoft.ActiveDirectory.Management.ADUser]
        ${Identity}
        ,
        [Parameter(ParameterSetName='Identity')]
        [ValidateNotNullOrEmpty()]
        [string]
        ${Partition}
        ,
        [ValidateNotNullOrEmpty()]
        [string]
        ${Server}
        ,
        [ValidateNotNullOrEmpty()]
        [pscredential]
        [System.Management.Automation.CredentialAttribute()]
        ${Credential}
        ,
        [Microsoft.ActiveDirectory.Management.ADAuthType]
        ${AuthType}
    )
    begin
    {
        try 
        {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer)) 
            {
                $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Get-ADUser', [System.Management.Automation.CommandTypes]::Cmdlet)
            $wrappedCmd2 = {
                [CmdletBinding(DefaultParameterSetName='Filter')]
                param(
                    [Parameter(ParameterSetName='Filter', Mandatory=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]
                    ${Filter}
                    ,
                    [Parameter(ParameterSetName='LdapFilter', Mandatory=$true)]
                    [ValidateNotNullOrEmpty()]
                    [string]
                    ${LDAPFilter}
                    ,
                    [Alias('Property')]
                    [ValidateNotNullOrEmpty()]
                    [string[]]
                    ${Properties}
                    ,
                    [Parameter(ParameterSetName='Filter')]
                    [Parameter(ParameterSetName='LdapFilter')]
                    [ValidateRange(0, 2147483647)]
                    [ValidateNotNullOrEmpty()]
                    [int]
                    ${ResultPageSize}
                    ,
                    [Parameter(ParameterSetName='LdapFilter')]
                    [Parameter(ParameterSetName='Filter')]
                    [System.Nullable[int]]
                    ${ResultSetSize}
                    ,
                    [Parameter(ParameterSetName='LdapFilter')]
                    [Parameter(ParameterSetName='Filter')]
                    [ValidateNotNull()]
                    [string]
                    ${SearchBase}
                    ,
                    [Parameter(ParameterSetName='Filter')]
                    [Parameter(ParameterSetName='LdapFilter')]
                    [ValidateNotNullOrEmpty()]
                    [Microsoft.ActiveDirectory.Management.ADSearchScope]
                    ${SearchScope}
                    ,
                    [Parameter(ParameterSetName='Identity', Mandatory=$true, Position=0, ValueFromPipeline=$true)]
                    [ValidateNotNull()]
                    [Microsoft.ActiveDirectory.Management.ADUser]
                    ${Identity}
                    ,
                    [Parameter(ParameterSetName='Identity')]
                    [ValidateNotNullOrEmpty()]
                    [string]
                    ${Partition}
                    ,
                    [ValidateNotNullOrEmpty()]
                    [string]
                    ${Server}
                    ,
                    [ValidateNotNullOrEmpty()]
                    [pscredential]
                    [System.Management.Automation.CredentialAttribute()]
                    ${Credential}
                    ,
                    [Microsoft.ActiveDirectory.Management.ADAuthType]
                    ${AuthType}
                )
                try {
                    & $wrappedCmd @PSBoundParameters 
                } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
                    $null
                }
            } 
            $scriptCmd = {& $wrappedCmd2 @PSBoundParameters} 
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
            $CopyOfSP = $steppablePipeline
        } catch {
            throw
        }
    }
    process
    {
        try {
            $steppablePipeline.Process($_)
        } catch {
            throw
        }
    }

    end
    {
        try {
            $steppablePipeline.End()
        } catch {
            throw
        }
    }
    <#

    .ForwardHelpTargetName Get-ADUser
    .ForwardHelpCategory Cmdlet

    #>
}
Johan
  • 74,508
  • 24
  • 191
  • 319
JohnLBevan
  • 22,735
  • 13
  • 96
  • 178
  • Sorry if this should be going to code review; when I started asking the question I'd not hit upon the solution, and since the solution I found seems very hacky I felt it's suitable for either community. – JohnLBevan May 13 '16 at 09:09
  • I'm voting to close this question as off-topic because this question belongs on http://codereview.stackoverflow.com – Johan May 14 '16 at 15:10
  • 3
    @Johan _Why_ do you believe that this question belongs on Code Review? And more importantly, what rule in the [help/on-topic] does it break such that it is off-topic for Stack Overflow? – 200_success May 14 '16 at 15:51
  • no MCVE, OP has already found a solution and it does not seem to be a practical answerable problem. – Johan May 14 '16 at 16:25

1 Answers1

1

Wow, this was a 'fun' one. Thanks OP for your post and base code.

I did some work from your original process, and after generating the code from the [System.Management.Automation.ProxyCommand]::Create((New-Object System.Management.Automation.CommandMetaData (Get-Command Get-ADUser))) command, I had the same problem as you when trying to change the $ScriptCmd - so I moved on to changing the Catch in the Process {...} block. It took a while, but I pulled on a few threads, and got it working.

Originally it was successfully processing whatever code I put in the catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] - but still throwing the ADIdentityNotFoundException exception, until I added a second catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] in the end {...} block.

It now successfully returns $null if the user doesn't exist, without throwing an exception, and so far has worked perfectly in an IF statement.

My full code is

# Proxy Function for custom ADIdentityNotFound exception handling
# Based on https://stackoverflow.com/questions/37205808/powershell-proxy-function-adding-exception-handling
function Get-ADUserWrapped {
      [CmdletBinding(DefaultParameterSetName='Filter', HelpUri='http://go.microsoft.com/fwlink/?LinkId=301397')]
      param()


      dynamicparam
      {
      try {
            $targetCmd = $ExecutionContext.InvokeCommand.GetCommand('ActiveDirectory\Get-ADUser', [System.Management.Automation.CommandTypes]::Cmdlet, $PSBoundParameters)
            $dynamicParams = @($targetCmd.Parameters.GetEnumerator() | Microsoft.PowerShell.Core\Where-Object { $_.Value.IsDynamic })
            if ($dynamicParams.Length -gt 0)
            {
                  $paramDictionary = [Management.Automation.RuntimeDefinedParameterDictionary]::new()
                  foreach ($param in $dynamicParams)
                  {
                  $param = $param.Value

                  if(-not $MyInvocation.MyCommand.Parameters.ContainsKey($param.Name))
                  {
                        $dynParam = [Management.Automation.RuntimeDefinedParameter]::new($param.Name, $param.ParameterType, $param.Attributes)
                        $paramDictionary.Add($param.Name, $dynParam)
                  }
                  }
                  return $paramDictionary
            }
      } catch {
            throw
      }
      }

      begin
      {
      try {
            $outBuffer = $null
            if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))
            {
                  $PSBoundParameters['OutBuffer'] = 1
            }
            $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('ActiveDirectory\Get-ADUser', [System.Management.Automation.CommandTypes]::Cmdlet)
            $scriptCmd = { & $wrappedCmd @PSBoundParameters }
            $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)
            $steppablePipeline.Begin($PSCmdlet)
      } catch {
      }
      }

      process
      {
      try {
            $steppablePipeline.Process($_)
            } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
            } catch { throw }
      }

      end
      {
      try {
            $steppablePipeline.End()
      } catch [Microsoft.ActiveDirectory.Management.ADIdentityNotFoundException] {
      } catch { throw }
      }
      <#

      .ForwardHelpTargetName ActiveDirectory\Get-ADUser
      .ForwardHelpCategory Cmdlet

#>
}

Since it was successful, thought I should make my first StackOverflow post to help others. It's definitely been added to my personal collection of snippets.

And since it's my first post, please forgive any breaches of ettiquette.

  • Nice work - much cleaner than my original code. Good use of dynamic parameters too; I've not yet delved around in that area. – JohnLBevan Nov 09 '22 at 08:52
  • 1
    @JohnLBevan - I'll be honest, the Dynamic Parameters bit was spit out automatically by the `[System.Management.Automation.ProxyCommand]::Create((New-Object System.Management.Automation.CommandMetaData (Get-Command Get-ADUser)))` command - but thanks! – Daniel Doecke Nov 10 '22 at 00:53
  • Ah cool - they've amended the code for that cmdlet since I'd used it then; good to know. – JohnLBevan Nov 10 '22 at 09:30