How long do Cmdlet instances persist?

Viewed 48

Was interested in what a custom implementation of a derived Cmdlet would look like, really to see how much work was involved. Mocked up the following:

using System.Management.Automation;

namespace EMSEngineering
{
    [Cmdlet(VerbsDiagnostic.Test, "Cmdlet")]
    public class TestCmdlet : Cmdlet
    {
        static int nextIndex = 0;

        private int index = nextIndex;

        public TestCmdlet()
        {
            System.Console.WriteLine("instantiating {0}", index);
            nextIndex++;
        }

        ~ TestCmdlet()
        {
            System.Console.WriteLine("terminating {0}", index);
        }

        [Parameter(Mandatory = true, ValueFromPipeline = true)]
        public string[] SomeParameter { get; set; }

        protected override void BeginProcessing()
        {
            if (SomeParameter != null)
                WriteVerbose(string.Format("begin processing {0} params", SomeParameter.Length));
        }

        protected override void ProcessRecord()
        {
            foreach (string param in SomeParameter)
                WriteObject(string.Format("processing {0}", param));
        }

        protected override void EndProcessing()
        {
            if (SomeParameter != null)
                WriteVerbose(string.Format("end processing {0} params", SomeParameter.Length));
        }

        protected override void StopProcessing()
        {
            WriteVerbose("stop processing");
        }
    }
}

Nothing fancy here, but when calling at the prompt, I noticed something unexpected.

PS E:\users\bitbucket\crmcall\Build_PI> '1','2' | Test-Cmdlet
instantiating 0
processing 1
processing 2
PS E:\users\bitbucket\crmcall\Build_PI> '1','2' | Test-Cmdlet
instantiating 1
processing 1
processing 2
PS E:\users\bitbucket\crmcall\Build_PI> exit
terminating 1
terminating 0

Each call to my little mock Cmdlet spun up a new instance. They only terminated when I exited the shell. I tried this within a larger loop where this was repeated 10 or more times. Same result - new instance on each call and all terminated on shell exit.

Is this the case for all Cmdlets? Do they really just stack up until the shell is exited? Have I missed something in my implementation?

1 Answers
  • Indeed, every call to a cmdlet creates a new instance of the implementing .NET class.

  • These instances are eventually garbage-collected (the timing is not deterministic and it can take minutes until collection occurs), but you can force synchronous disposal by invoking the .NET garbage collector explicitly:

    [GC]::Collect(); [GC]::WaitForPendingFinalizers()
    

Note:

  • By default, the garbage collector runs periodically, and, as stated, it can take minutes until a given object is actually freed; however, the garbage collector also runs on demand, in low-memory situations, and even the frequency of the periodic runs is dependent on memory conditions (another way of putting it: there is no fixed frequency) - see the Fundamentals of garbage collection help topic for more information.

    • To give one example of the variability of the frequency: shortly after entering a PowerShell session the memory pressure is apparently higher, making near-instant garbage collection more likely.
  • In some situations garbage collection of cmdlet-implementing instances happens instantly:

    • If you invoke help via -?: Test-Cmdlet -? (though not via Get-Help Test-Cmdlet, which doesn't necessitate instantiating the cmdlet class).

    • If the invocation is syntactically invalid, such as when attempting to use an unknown parameter; e.g.: Test-Cmdlet -NosuchParam

    • Others?

Related