{"id":102676,"date":"2024-07-01T14:28:47","date_gmt":"2024-07-01T14:28:47","guid":{"rendered":"https:\/\/www.red-gate.com\/simple-talk\/?p=102676"},"modified":"2026-04-16T09:23:47","modified_gmt":"2026-04-16T09:23:47","slug":"extending-get-process-to-show-the-process-chain-using-powershell-proxy-functions","status":"publish","type":"post","link":"https:\/\/www.red-gate.com\/simple-talk\/sysadmin\/powershell\/extending-get-process-to-show-the-process-chain-using-powershell-proxy-functions\/","title":{"rendered":"PowerShell Proxy Functions: Extend Any Cmdlet Without Modifying It"},"content":{"rendered":"<p><strong>PowerShell proxy functions (also called wrapper functions) allow you to extend or modify any existing cmdlet without touching its source code. You create a new function with the same name and parameters as the original cmdlet, add your custom logic, and call the original through the proxy. This article demonstrates the technique by extending Get-Process: adding a -ShowChain switch parameter that retrieves the full parent-child process chain for each process using WMI, displaying process relationships that the standard Get-Process output doesn&#8217;t provide. The same proxy function pattern applies to any cmdlet you want to extend.<\/strong><\/p>\n<p>A proxy function or wrapper function allows you to deploy a custom version of a function or cmdlet while the internal functionality remains implemented. That means you can get a core cmdlet or function, add parameters (or remove), and write the code for these parameters, customizing a new version of this core cmdlet or function. Also, we get the begin, process and end block exposed from the PowerShell advanced functions, allowing control over the command steppable pipeline.<\/p>\n<p>The <a href=\"https:\/\/learn.microsoft.com\/en-us\/dotnet\/api\/system.management.automation.steppablepipeline\">SteppablePipeline<\/a> allows you to implement a cmdlet (advanced function) by delegating most of the implementation to another cmdlet in a memory-efficient, streaming manner.<\/p>\n<p>From this <a href=\"https:\/\/stackoverflow.com\/questions\/73053183\/what-good-does-a-steppable-pipeline\">stackoverflow post<\/a> by <a href=\"https:\/\/stackoverflow.com\/users\/45375\/mklement0\">mklement0<\/a>: \u201cSpecifically, a steppable pipeline allows you to delegate the implementation of your proxy function to a script block whose life cycle is kept in sync with the proxy function itself, in terms of initialization (begin block), per-object pipeline input processing (process block), and termination (end block), which means that the single instantiation of the wrapped cmdlet is in effect directly connected to the same pipeline as the proxy function itself.\u201d<\/p>\n<p>Let\u00b4s create a scenario with SQL Server. If you use <code>xp_cmdshell<\/code> to call the notepad executable, your session will be hanging until you kill the process in the OS, which can be done with Task Manager. Execute the following if you want to try it:<\/p>\n<pre class=\"lang:tsql theme:ssms2012-simple-talk\">xp_cmdshell 'NOTEPAD'<\/pre>\n<p>You will see something similar to:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone wp-image-102677\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-1.png\" alt=\"SQL Query 2\" width=\"493\" height=\"219\" srcset=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-1.png 493w, https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-1-300x133.png 300w\" sizes=\"auto, (max-width: 493px) 100vw, 493px\" \/><\/p>\n<p>The app will not open a window on your machine, but if you go to task manager you will see it, and you can kill it.<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"365\" height=\"365\" class=\"wp-image-102678\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-2.png\" \/><\/p>\n<p>Or, if you want to see the complete chain using <a href=\"https:\/\/learn.microsoft.com\/en-us\/sysinternals\/downloads\/process-explorer\">Process Explorer<\/a>:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"786\" height=\"593\" class=\"wp-image-102679\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-3.png\" \/><\/p>\n<p>But using PowerShell, <code>Get-Process<\/code>:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Get-Process<\/pre>\n<p>It\u00b4s not listed:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"426\" height=\"124\" class=\"wp-image-102680\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-4.png\" \/><\/p>\n<p>It would be fantastic if we could get the process chain in the <code>Get-Process<\/code> return values, so we will add a new parameter called <code>ShowChain<\/code> to <code>Get-Process<\/code> to extend its functionality.<\/p>\n<p><em>Note: the final code is very long, and the snippets are available in the article for browsing and understanding the process. You can download the final working code (in a .txt file) from the Simple Talk site <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/Laerte-Junior-Get-Process.txt\">here.<\/a><\/em><\/p>\n<h2>FIRST STEP &#8211; GET THE METADATA FROM GET-PROCESS<\/h2>\n<p>The first step is to get the metadata from Get-Process and saved to a file called Get-Process.txt in the temp folder to check how it works:<\/p>\n<pre class=\"lang:ps theme:powershell-ise \">$Cmd = Get-Command Get-Process\n$CmdMetadata = New-Object System.Management.Automation.CommandMetaData $Cmd \n[System.Management.Automation.ProxyCommand]::Create($CmdMetadata) | \n     Out-File C:\\temp\\Get-Process.txt   \n\n<\/pre>\n<p>The output file will be the following:<\/p>\n<pre class=\"lang:none theme:none\">[CmdletBinding(DefaultParameterSetName='Name', HelpUri='https:\/\/go.microsoft.com\/fwlink\/?LinkID=113324', RemotingCapability='SupportedByCommand')]\nparam(\n    [Parameter(ParameterSetName='NameWithUserName', Position=0, ValueFromPipelineByPropertyName=$true)]\n    [Parameter(ParameterSetName='Name', Position=0, ValueFromPipelineByPropertyName=$true)]\n    [Alias('ProcessName')]\n    [ValidateNotNullOrEmpty()]\n    [string[]]\n    ${Name},\n    [Parameter(ParameterSetName='IdWithUserName', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]\n    [Parameter(ParameterSetName='Id', Mandatory=$true, ValueFromPipelineByPropertyName=$true)]\n    [Alias('PID')]\n    [int[]]\n    ${Id},\n    [Parameter(ParameterSetName='InputObjectWithUserName', Mandatory=$true, ValueFromPipeline=$true)]\n    [Parameter(ParameterSetName='InputObject', Mandatory=$true, ValueFromPipeline=$true)]\n    [System.Diagnostics.Process[]]\n    ${InputObject},\n    [Parameter(ParameterSetName='NameWithUserName', Mandatory=$true)]\n    [Parameter(ParameterSetName='IdWithUserName', Mandatory=$true)]\n    [Parameter(ParameterSetName='InputObjectWithUserName', Mandatory=$true)]\n    [switch]\n    ${IncludeUserName},\n    [Parameter(ParameterSetName='Id', ValueFromPipelineByPropertyName=$true)]\n    [Parameter(ParameterSetName='Name', ValueFromPipelineByPropertyName=$true)]\n    [Parameter(ParameterSetName='InputObject', ValueFromPipelineByPropertyName=$true)]\n    [Alias('Cn')]\n    [ValidateNotNullOrEmpty()]\n    [string[]]\n    ${ComputerName},\n    [Parameter(ParameterSetName='Id')]\n    [Parameter(ParameterSetName='InputObject')]\n    [Parameter(ParameterSetName='Name')]\n    [ValidateNotNull()]\n    [switch]\n    ${Module},\n    [Parameter(ParameterSetName='InputObject')]\n    [Parameter(ParameterSetName='Id')]\n    [Parameter(ParameterSetName='Name')]\n    [Alias('FV','FVI')]\n    [ValidateNotNull()]\n    [switch]\n    ${FileVersionInfo})\nbegin\n{\n    try {\n        $outBuffer = $null\n        if ($PSBoundParameters.TryGetValue('OutBuffer', [ref]$outBuffer))\n        {\n            $PSBoundParameters['OutBuffer'] = 1\n        }\n        $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand('Microsoft.PowerShell.Management\\Get-Process', [System.Management.Automation.CommandTypes]::Cmdlet)\n        $scriptCmd = {&amp; $wrappedCmd @PSBoundParameters }\n        $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)\n        $steppablePipeline.Begin($PSCmdlet)\n    } catch {\n        throw\n    }\n}\nprocess\n{\n    try {\n        $steppablePipeline.Process($_)\n    } catch {\n        throw\n    }\n}\nend\n{\n    try {\n        $steppablePipeline.End()\n    } catch {\n        throw\n    }\n}\n&lt;#\n.ForwardHelpTargetName Microsoft.PowerShell.Management\\Get-Process\n.ForwardHelpCategory Cmdlet\n#&gt;<\/pre>\n<p>Using the words of my good friend <a href=\"https:\/\/devblogs.microsoft.com\/scripting\/proxy-functions-spice-up-your-powershell-core-cmdlets\/\">Shay Levy<\/a> :<\/p>\n<p><em>\u201cDon\u2019t get intimidated by the result. This is what a proxy function looks like. As you can see, the generated code starts by including all the cmdlet parameters and then defines the script blocks of the command. At the end, there are instructions for the Get-Help cmdlet.<\/em><\/p>\n<p><em>In the Begin block, the Get-Process command ($wrappedCmd) is retrieved, and the parameters are passed to it (@PSBoundParameters). A steppable pipeline is initialized ($steppablePipeline), which invokes the cmdlet ($scriptCmd), and finally, the Begin starts. At the end of the code sample, you can find a multiline comment that is used to instruct the Help that Get-Help should display when you type Get-Help &lt;ProxyFunction&gt;.\u201d<\/em><\/p>\n<p>In this script, add the following:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\"><code>Function  Get-Process {<\/code> <code>} <\/code><\/pre>\n<p>Our new cmdlet will have the same name of the core cmdlet <code>Get-Process<\/code> . It\u00b4s not a problem as we will see later in this article.\u00a0Now it\u2019s time to create the Function that will retrieve the Process Chain :<\/p>\n<h2>SECOND STEP &#8211; CREATE THE PARAMETER<\/h2>\n<p>The next step is create the switch parameter <code>ShowChain:<\/code><\/p>\n<pre class=\"lang:ps theme:powershell-ise \">           [ValidateNotNull()]\n           [switch]\n           ${ShowChain}) \n\n<\/pre>\n<p>It is inserted the code thusly:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1215\" height=\"628\" class=\"wp-image-102681\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-5.png\" \/><\/p>\n<h2>THIRD STEP &#8211; CREATE THE FUNCTION WITH THE PROCESS CHAIN.<\/h2>\n<p>The function gets the <code>ProcesseId<\/code> column from <code>Get-Process<\/code> and query the WMI <code>win32_process<\/code> to get the child process because <code>Get-Process<\/code> does not return the <code>ParentID<\/code> column. Then it creates a loop to find the complete chain showing in the yellow color and with \u2018-\u2018to hierarchical show.<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">function Get-ProcessChain {\n    [cmdletbinding()]\n    Param(\n          [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName = $true)] [System.Diagnostics.Process]$ProcessObject\n    )\n    Begin {\n        $DefaultFColor = $Host.UI.RawUI.ForegroundColor\n        $ArrayContains = @()\n    }\n    process { \n        if ($ArrayContains -notcontains  $ProcessObject.ID ) {\n            $ProcessObject\n            if ($ProcessObject.name -ne \u2018services\u2019) {\n                $ComputerName = $ProcessObject.MachineName\n                $Beforecaption = \u2018-\u2018\n                $ProcessMotherID = $ProcessObject.ID\n                do {\n                    $Hasharguments = @{\n                        Filter = \u201cParentProcessId='$($ProcessMotherID)'\u201d\n                        Property = \"ProcessID,Name\"\n                        ErrorAction = \"ProcessID,Name\"\n                        ComputerName = $ComputerName\n                    }\n                    $ProcessChild = Get-WmiObject win32_process @Hasharguments\n                    if ($ProcessChild ) {\n                        $Beforecaption += \u2018-\u2018\n                        $Host.UI.RawUI.ForegroundColor = \u201cYellow\u201d\n                        $ProcessChild | \n                        ForEach-Object {\n                            Get-Process -Id $_.Processid -ComputerName $ComputerName -ErrorAction SilentlyContinue  |  \n                            Add-Member -MemberType NoteProperty  -name ProcessName -Value \u201c$Beforecaption $($_.Name)\u201d -Force -PassThru            \n                            $ProcessMotherID = $_.Processid\n                            $ArrayContains+= $_.Processid\n                        }\n                    } else {    \n                        $Beforecaption = \u2018-\u2018\n                        Break\n                    }     \n                }  while($true)\n             }     \n                 $Host.UI.RawUI.ForegroundColor = $DefaultFColor\n         }     \n    }\n} \n<\/pre>\n<h3>Using the same names<\/h3>\n<p>The Proxy Function using the same name of the core cmdlet is not a problem because the function takes precedence in the running, also having the same name, we have the capability of the command discovery process. Here\u00b4s the list of precedence :<\/p>\n<ul>\n<li>\u00b7 Alias: All Windows PowerShell aliases in the current session<\/li>\n<li>\u00b7 Filter\/Function: All Windows PowerShell functions<\/li>\n<li>\u00b7 Cmdlet: The cmdlets in the current session (\u201cCmdlet\u201d is the default)<\/li>\n<li>\u00b7 ExternalScript: All .ps1 files in the paths that are listed in the Path environment variable ($env:PATH)<\/li>\n<li>\u00b7 Application: All non-Windows-PowerShell files in paths that are listed in the Path environment variable<\/li>\n<li>\u00b7 Script: Script blocks in the current session<\/li>\n<\/ul>\n<p>The magic happens in the begin block of the function where I check if the <code>ShowChain<\/code> parameter was passed, remove it from the <code>$psboundparameters<\/code> because this parameter does not exist in the core <code>Get-Process<\/code> cmdlet. If I don\u2019t remove it will have an error when piping the function <code>Get-ProcessChain:<\/code><\/p>\n<pre class=\"lang:ps theme:powershell-ise\">        if ($PSBoundParameters[\u2018ShowChain\u2019]) {\n           [Void]$PSBoundParameters.Remove(\u201cShowChain\u201d)\n               $scriptCmd = {&amp; $wrappedCmd @PSBoundParameters | Get-ProcessChain}\n           } else {\n               $scriptCmd = {&amp; $wrappedCmd @PSBoundParameters}<\/pre>\n<p>It is finished, and the final code\u00a0 follows:<\/p>\n<p><em>Reminder: this code is available (in a .txt file), from the Simple Talk site <a href=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/Laerte-Junior-Get-Process.txt\">here.<\/a><\/em><\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Function Get-Process {\n \n       [CmdletBinding(DefaultParameterSetName=\u2018Name\u2019, HelpUri=\u2018http:\/\/go.microsoft.com\/fwlink\/?LinkID=113324&amp;#8217;' , RemotingCapability=\u2018SupportedByCommand\u2019)]\n       param(\n           [Parameter(ParameterSetName=\u2018Name\u2019, Position=0, ValueFromPipelineByPropertyName=$true)]\n           [Alias(\u2018ProcessName\u2019)]\n           [ValidateNotNullOrEmpty()]\n           [string[]]\n           ${Name},\n \n           [Parameter(ParameterSetName=\u2018Id\u2019, Mandatory=$true, ValueFromPipelineByPropertyName=$true)]\n           [Alias(\u2018PID\u2019)]\n           [int[]]\n           ${Id},\n \n           [Parameter(ValueFromPipelineByPropertyName=$true)]\n           [Alias(\u2018Cn\u2019)]\n           [ValidateNotNullOrEmpty()]\n           [string[]]\n           ${ComputerName},\n \n           [ValidateNotNull()]\n           [switch]\n           ${Module},\n \n           [Alias(\u2018FV\u2019,\u2018FVI\u2019)]\n           [ValidateNotNull()]\n           [switch]\n           ${FileVersionInfo},\n \n           [Parameter(ParameterSetName=\u2018InputObject\u2019, Mandatory=$true, ValueFromPipeline=$true)]\n           [System.Diagnostics.Process[]]\n           ${InputObject},\n            \n             [ValidateNotNull()]\n           [switch]\n           ${ShowChain})\n \n       begin  {\n            function Get-ProcessChain {\n                [cmdletbinding()]\n                Param(\n                    [Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true,ValueFromPipelineByPropertyName = $true)] [System.Diagnostics.Process]$ProcessObject\n                )\n                Begin {\n                    $DefaultFColor = $Host.UI.RawUI.ForegroundColor\n                    $ArrayContains = @()\n                }\n                process { \n                    if ($ArrayContains -notcontains  $ProcessObject.ID ) {\n                        $ProcessObject\n                        if ($ProcessObject.name -ne \u2018services\u2019) {\n                            $ComputerName = $ProcessObject.MachineName\n                            $Beforecaption = \u2018-\u2018\n                            $ProcessMotherID = $ProcessObject.ID\n                            do {\n                                $Hasharguments = @{\n                                    Filter = \u201cParentProcessId='$($ProcessMotherID)'\u201d\n                                    Property = \"ProcessID,Name\"\n                                    ErrorAction = \"SilentlyContinue\"\n                                    ComputerName = $ComputerName\n                                }\n                                $ProcessChild = Get-WmiObject win32_process @Hasharguments\n                                if ($ProcessChild ) {\n                                    $Beforecaption += \u2018-\u2018\n                                    $Host.UI.RawUI.ForegroundColor = \u201cYellow\u201d\n                                    $ProcessChild | \n                                    ForEach-Object {\n                                        Get-Process -Id $_.Processid -ComputerName $ComputerName -ErrorAction SilentlyContinue  |  \n                                        Add-Member -MemberType NoteProperty  -name ProcessName -Value \u201c$Beforecaption $($_.Name)\u201d -Force -PassThru            \n                                        $ProcessMotherID = $_.Processid\n                                        $ArrayContains+= $_.Processid\n                                    }\n                                } else {    \n                                    $Beforecaption = \u2018-\u2018\n                                    Break\n                                }     \n                            }  while($true)\n                             \n                           $Host.UI.RawUI.ForegroundColor = $DefaultFColor\n                        }     \n                    }\n                }\n           } \n           try {\n               $outBuffer = $null\n               if ($PSBoundParameters.TryGetValue(\u2018OutBuffer\u2019, [ref]$outBuffer))\n               {\n                   $PSBoundParameters[\u2018OutBuffer\u2019] = 1\n               }\n               $wrappedCmd = $ExecutionContext.InvokeCommand.GetCommand(\u2018Get-Process\u2019, [System.Management.Automation.CommandTypes]::Cmdlet)\n                if ($PSBoundParameters[\u2018ShowChain\u2019]) {\n                    [Void]$PSBoundParameters.Remove(\u201cShowChain\u201d)\n                    $scriptCmd = {&amp; $wrappedCmd @PSBoundParameters | Get-ProcessChain}\n                } else {\n                    $scriptCmd = {&amp; $wrappedCmd @PSBoundParameters}\n                }     \n               $steppablePipeline = $scriptCmd.GetSteppablePipeline($myInvocation.CommandOrigin)\n               $steppablePipeline.Begin($PSCmdlet)\n           } catch {\n               throw\n           }\n       }\n \n       process\n       {\n           try {\n               $steppablePipeline.Process($_)\n           } catch {\n               throw\n           }\n       }\n \n       end\n       {\n           try {\n               $steppablePipeline.End()\n           } catch {\n               throw\n           }\n       }\n}     \n&lt;#\n \n.ForwardHelpTargetName Get-Process\n.ForwardHelpCategory Cmdlet\n \n#&gt;<\/pre>\n<p>Then, our Proxy Function is ready to use. To load it into memory we can dot-sourcing<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">. C:\\temp\\get-process.ps1\nGet-Process -ShowChain<\/pre>\n<p>Or load it into your profile. I have in my profile a module called <code>Functions<\/code> where I store all the functions that I use in my day-to-day<\/p>\n<p>And calling the function:<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Get-Process -ShowChain<\/pre>\n<p>You will now see the subordinate processes:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"859\" height=\"721\" class=\"wp-image-102682\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-6.png\" \/><\/p>\n<p>And calling only the SQL Server process the process chain will be visible<\/p>\n<pre class=\"lang:ps theme:powershell-ise\">Get-Process sqlservr -ShowChain<\/pre>\n<p>Which will cause the following to be shown (if you have not killed the <code>notepad<\/code> instance from the start of the article):<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"537\" height=\"239\" class=\"wp-image-102683\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-7.png\" \/><\/p>\n<p>And killing the process 10284, which in my connection, corresponds to Notepad the SQL Server session:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"775\" height=\"153\" class=\"wp-image-102684\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-8.png\" \/><\/p>\n<p>Causes the xp_<code>cmdshell<\/code> to return with a <code>NULL<\/code> output:<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" width=\"1366\" height=\"736\" class=\"wp-image-102685\" src=\"https:\/\/www.red-gate.com\/simple-talk\/wp-content\/uploads\/2024\/06\/word-image-102676-9.png\" \/><\/p>\n<p>Proxy functions are very useful if you want to customize the core cmdlets instead of creating a new hoke function for that.<\/p>\n<p>Note: Do not use PowerShell ISE. It messes with the colors, instead of dot-source (as we saw before), the function and calls in the PowerShell console<\/p>\n<p>References<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/scripting\/proxy-functions-spice-up-your-powershell-core-cmdlets\/\">Proxy Functions: Spice Up Your PowerShell Core Cmdlets (Shay Levy)<\/a><\/p>\n\n\n<section id=\"faq\" class=\"faq-block my-5xl\">\n    <h2>FAQs<\/h2>\n\n                        <h3 class=\"mt-4xl\">1. What is a PowerShell proxy function?<\/h3>\n            <div class=\"faq-answer\">\n                <p>A proxy function (or wrapper function) in PowerShell is a custom function that wraps an existing cmdlet &#8211; reusing all its original parameters and functionality while adding custom logic before, during, or after the cmdlet&#8217;s execution. The proxy function is created using the cmdlet&#8217;s metadata (retrieved with Get-Command) as a scaffold, then modified to add new parameters or behaviour. Because it uses the original cmdlet&#8217;s parameter definitions, it inherits all tab completion and help integration.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">2. How do I extend a PowerShell cmdlet without modifying its source?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Use a proxy function: retrieve the cmdlet&#8217;s metadata using (Get-Command Get-Process).CommandMetadata, generate a proxy scaffold with [System.Management.Automation.ProxyCommand]::Create($meta), add your new parameters to the Param() block, and inject your custom logic into the Begin, Process, or End scriptblocks. Place the finished function in your PowerShell profile or a module to override the original cmdlet name in your session.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">3. Can a PowerShell proxy function use the same name as the cmdlet it wraps?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Yes &#8211; and this is often intentional. When a PowerShell function has the same name as a cmdlet, the function takes precedence in command resolution. This means users of the proxy function get the extended behaviour transparently, without changing any scripts or workflows that call the original cmdlet name. The original cmdlet remains available as Microsoft.PowerShell.ManagementGet-Process (fully qualified) if you need to call it directly from within the proxy.<\/p>\n            <\/div>\n                    <h3 class=\"mt-4xl\">4. How do I get the parent process in PowerShell?<\/h3>\n            <div class=\"faq-answer\">\n                <p>Get-Process does not return the ParentProcessId natively. Use WMI: Get-WmiObject Win32_Process -Filter &#8220;ProcessId = $pid&#8221; | Select-Object ParentProcessId, or on PowerShell 7: Get-CimInstance Win32_Process. To get the full process chain (parent, grandparent, etc.), recursively query Win32_Process using the parent&#8217;s ProcessId until no further parent is found &#8211; which is the approach demonstrated in this article&#8217;s proxy function.<\/p>\n            <\/div>\n            <\/section>\n","protected":false},"excerpt":{"rendered":"<p>PowerShell proxy functions let you extend or modify any existing cmdlet without changing its source &#8211; add new parameters, change output format, or add pre\/post processing. Demonstrated by extending Get-Process to display the full parent-child process chain.&hellip;<\/p>\n","protected":false},"author":221715,"featured_media":102677,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":true,"footnotes":""},"categories":[35],"tags":[4635],"coauthors":[6819],"class_list":["post-102676","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-powershell","tag-powershell"],"acf":[],"_links":{"self":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102676","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/users\/221715"}],"replies":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/comments?post=102676"}],"version-history":[{"count":13,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102676\/revisions"}],"predecessor-version":[{"id":109921,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/posts\/102676\/revisions\/109921"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media\/102677"}],"wp:attachment":[{"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/media?parent=102676"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/categories?post=102676"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/tags?post=102676"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.red-gate.com\/simple-talk\/wp-json\/wp\/v2\/coauthors?post=102676"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}