powershellproxy-classes

PowerShell Proxy Function; Adding Exception Handling


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

    #>
}

Solution

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