Public/Start-TcsTelemetry.ps1

<#
.SYNOPSIS
    Starts timing one run of a command for telemetry and returns a token for Complete-TcsTelemetry.

.DESCRIPTION
    The Start-TcsTelemetry function is the first half of the telemetry wrapper for commands in
    tcs modules. Call it once at the start of a command (in the begin block of a pipeline
    function), keep the token it returns, and pass the token to Complete-TcsTelemetry when the
    command ends: in a catch block with -ErrorRecord when the command fails, and in a finally
    (or end) block otherwise. Complete-TcsTelemetry does nothing for a token that is already
    complete, so completing it in both places is safe.

    The command, module and version are taken from the calling command when they are not
    given.

    Only the outermost run is reported. When an exported command calls another exported
    command of the same module, the inner run returns a token with IsOutermost = $false and
    sends nothing, so one user action is one event. Commands of other modules are reported
    separately.

    Telemetry never breaks the caller: if anything goes wrong, a token is still returned and
    the failure is written to the verbose stream. Nothing is sent when telemetry is turned
    off; see Invoke-TelemetryCollection.

.PARAMETER CommandName
    The name reported for the command. Defaults to the name of the calling command.

.PARAMETER ModuleName
    The module the command belongs to. Defaults to the module of the calling command.

.PARAMETER ModuleVersion
    The module version. Defaults to the version of the calling command's module.

.INPUTS
    None
    This function does not accept pipeline input.

.OUTPUTS
    Tcs.TelemetryToken
    Id, CommandName, ModuleName, ModuleVersion, IsOutermost, Failed, Completed and Exception.
    Assign it to a variable so it is not written to the pipeline.

.EXAMPLE
    function Get-Widget {
        [CmdletBinding()]
        param([string]$Name)
        $telemetry = Start-TcsTelemetry
        try {
            Get-Item -Path $Name -ErrorAction Stop
        }
        catch {
            Complete-TcsTelemetry -Token $telemetry -ErrorRecord $_
            throw
        }
        finally {
            Complete-TcsTelemetry -Token $telemetry
        }
    }

    A command without pipeline blocks: the run is completed as failed in catch, and as
    successful in finally otherwise.

.EXAMPLE
    function Set-Widget {
        [CmdletBinding()]
        param([Parameter(ValueFromPipeline)][string]$Name)
        begin {
            $telemetry = Start-TcsTelemetry
            $lastError = $null
        }
        process {
            $completed = $false
            try {
                Set-Thing -Name $Name -ErrorAction Stop
                $completed = $true
            }
            catch {
                $lastError = $_
                throw
            }
            finally {
                if (-not $completed) {
                    Complete-TcsTelemetry -Token $telemetry -ErrorRecord $lastError
                }
            }
        }
        end {
            Complete-TcsTelemetry -Token $telemetry -ErrorRecord $lastError
        }
    }

    A pipeline function: one event is sent for the whole pipeline run. The end block does not
    run when a later command stops the pipeline (for example Select-Object -First), or after
    $PSCmdlet.ThrowTerminatingError or $PSCmdlet.WriteError with -ErrorAction Stop, which also
    skip catch. The finally block completes the run in those cases. Set $lastError before
    calling $PSCmdlet.ThrowTerminatingError so the run is reported as failed.

.NOTES
    Author: Nigel Tatschner
    Company: TheCodeSaiyan

.LINK
    Complete-TcsTelemetry
#>

function Start-TcsTelemetry {
    [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '',
        Justification = 'Only starts an in-memory timer; nothing on the system is changed.')]
    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(HelpMessage = 'Name reported for the command.')]
        [string]$CommandName,

        [Parameter(HelpMessage = 'Module the command belongs to.')]
        [string]$ModuleName,

        [Parameter(HelpMessage = 'Version of the module.')]
        [string]$ModuleVersion
    )

    $invocation = $null
    try {
        $callStack = @(Get-PSCallStack)
        if ($callStack.Count -gt 1) {
            $invocation = $callStack[1].InvocationInfo
        }
        return (New-TcsTelemetryToken -Invocation $invocation -CommandName $CommandName -ModuleName $ModuleName -ModuleVersion $ModuleVersion)
    }
    catch {
        Write-Verbose "Telemetry could not be started: $($_.Exception.Message)"
        # A token that sends nothing, so the caller's Complete-TcsTelemetry still works
        return [PSCustomObject]@{
            PSTypeName    = 'Tcs.TelemetryToken'
            Id            = [guid]::NewGuid().ToString()
            CommandName   = $CommandName
            ModuleName    = $ModuleName
            ModuleVersion = $ModuleVersion
            IsOutermost   = $false
            Failed        = $false
            Completed     = $false
            Exception     = $null
        }
    }
}