PSAppInsights.psm1

<#
    PowerShell App Insights Module
    V0.7.1
    Application Insight Tracing to Powershell Scripts and Modules
 
Documentation :
    Ref .Net : https://msdn.microsoft.com/en-us/library/microsoft.applicationinsights.aspx
    Ref JS : https://github.com/Microsoft/ApplicationInsights-JS/blob/master/API-reference.md
#>


$Global:AISingleton = @{
    ErrNoClient = "Client - No Application Insights Client specified or initialized."
    Configuration = $null    
    #The current AI Client
    Client = $null
    #The Perfmon Collector
    PerformanceCollector = $null
    #QuickPulse aka Live Metrics Stream
    QuickPulse = $null
    #Stack of current Operations
    Operations = [System.Collections.Stack]::new()
}

<#
.Synopsis
   Start a new AI Client
.DESCRIPTION
   Long description
.EXAMPLE
   Example of how to use this cmdlet
 
#>

function New-AIClient
{
    [CmdletBinding()]
    [Alias('New-AISession')]
    [OutputType([Microsoft.ApplicationInsights.TelemetryClient])]
    Param
    (
        # The Instrumentation Key for Application Analytics
        [Parameter(Mandatory=$true,
                   ValueFromPipelineByPropertyName=$true,
                   Position=0)]
        [Alias("Key")]
        $InstrumentationKey,
        [string]$SessionID = (New-Guid), 
        [string]$OperationID = (New-Guid), #? Base 64 encoded GUID ?
        #Version of the application or Component
        $Version,
        # Set to indicate messages sent from or during a test
        [string]$Synthetic = $null,

        #Set of initializers - Default: Operation Correlation is enabled

        [Alias("Init")]
        [ValidateSet('Domain','Device','Operation','Dependency')]
        [String[]] $Initializer = @(), 
        
        #Allow PII in Traces
        [switch]$AllowPII,

        #Send AI traces via Fiddler for debugging
        [switch]$Fiddler
    )

    Process
    {
        try { 
            Write-Verbose "create Telemetry client"

            # This is a singleton that controls all New AI Client sessions for this process from this moment
            [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.InstrumentationKey = $InstrumentationKey
            [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.DisableTelemetry = $false

            #optionally add Fiddler for debugging
            if ($fiddler) { 
                [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.TelemetryChannel.EndpointAddress = 'http://localhost:8888/v2/track'
            }
            
            $Global:AISingleton.Configuration = [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active
            # Start the initialisers specified
            if ($Initializer.Contains('Operation')) {
                #Initializer for operation correlation
                $OpInit = [Microsoft.ApplicationInsights.Extensibility.OperationCorrelationTelemetryInitializer]::new()
                $Global:AISingleton.Configuration.TelemetryInitializers.Add($OpInit)
            }
            #Add domain initialiser to add domain and machine info
            if ($Initializer.Contains('Domain')) {
                $DomInit = [Microsoft.ApplicationInsights.WindowsServer.DomainNameRoleInstanceTelemetryInitializer]::new()
                $Global:AISingleton.Configuration.TelemetryInitializers.Add($DomInit)
            }
            #Add device initiliser to add client info
            if ($Initializer.Contains('Device')) {
                $DeviceInit = [Microsoft.ApplicationInsights.WindowsServer.DeviceTelemetryInitializer]::new()
                $Global:AISingleton.Configuration.TelemetryInitializers.Add($DeviceInit)
            }

            #Add dependency collector to (automatically ?) measure dependencies
            if ($Initializer.Contains('Dependency')) {
                $Dependency = [Microsoft.ApplicationInsights.DependencyCollector.DependencyTrackingTelemetryModule]::new();
                $TelemetryModules = [Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryModules]::Instance;
                $TelemetryModules.Modules.Add($Dependency);
            }

            #Now that they are added, they still need to be initialised
            #Lets do it
            $Global:AISingleton.Configuration.TelemetryInitializers | 
                Where-Object {$_ -is 'Microsoft.ApplicationInsights.Extensibility.ITelemetryModule'} |
                ForEach-Object { $_.Initialize($Global:AISingleton.Configuration); }
            $Global:AISingleton.Configuration.TelemetryProcessorChain.TelemetryProcessors |
                 Where-Object {$_ -is 'Microsoft.ApplicationInsights.Extensibility.ITelemetryModule'} |
                  ForEach-Object { $_.Initialize($Global:AISingleton.Configuration); }
            $TelemetryModules = [Microsoft.ApplicationInsights.Extensibility.Implementation.TelemetryModules]::Instance;
            $TelemetryModules.Modules | 
                Where-Object {$_ -is 'Microsoft.ApplicationInsights.Extensibility.ITelemetryModule'} |
                ForEach-Object { $_.Initialize($Global:AISingleton.Configuration); }
            #Time to start the client
            $client = [Microsoft.ApplicationInsights.TelemetryClient]::new($Global:AISingleton.Configuration)

            if ($client) { 
                Write-Verbose "Add Key, Session.id and Operation.id"
                
                $client.InstrumentationKey = $InstrumentationKey
                $client.Context.Session.Id = $SessionID
                #Operation : A generated value that correlates different events, so that you can find "Related items"
                $client.Context.Operation.Id = $OperationID

                #do some standard init on the context
                # set properties such as TelemetryClient.Context.User.Id to track users and sessions,
                # or TelemetryClient.Context.Device.Id to identify the machine.
                # This information is attached to all events sent by the instance.
                
                Write-Verbose "Add device.OS and User Agent"
                #OS cannot be read in Azure automation, handle gracefully
                $OS = Get-CimInstance -ClassName 'Win32_OperatingSystem' -ErrorAction SilentlyContinue
                if ($OS) {
                    $client.Context.Device.OperatingSystem = $OS.version
                }
                $client.Context.User.UserAgent = $Host.Name

                if ($AllowPII) {
                    Write-Verbose "Add PII user and computer information"

                    #Only if Explicitly noted
                    $client.Context.Device.Id = $env:COMPUTERNAME 
                    $client.Context.User.Id = $env:USERNAME 
                } else { 
                    Write-Verbose "Add NON-PII user and computer identifiers"
                    #Default to NON-PII
                    $client.Context.Device.Id = (Get-StringHash -String $env:COMPUTERNAME -HashType MD5).hash 
                    $client.Context.User.Id = (Get-StringHash -String $env:USERNAME -HashType MD5).hash  
                }
                if ($Global:AISingleton.Client -ne $null ) {
                    Write-Verbose "replacing active telemetry client"
                    Flush-AIClient -Client $Global:AISingleton.Client
                    $Global:AISingleton.Client = $null
                } 
                #Save client in Global for re-use when not specified
                $Global:AISingleton.Client = $client

                if ($Version ) {
                    write-verbose "use specified version"
                    $client.Context.Component.Version = [string]($version)
                } else {
                    write-verbose "retrieve version of calling script or module."
                    $client.Context.Component.Version = [string](getCallerVersion -level 2)
                }

                #Indicate actual / Synthethic events
                $Global:AISingleton.Client.Context.Operation.SyntheticSource = $Synthetic

                return $client 
            } else { 
                Throw "Could not create ApplicationInsights Client"
            }
        } catch {
            Throw "Could not create ApplicationInsights Client"
        }
    }
}


<#
.Synopsis
    Flush the Application Insights Queue to the AI Service
     
#>

function Push-AIClient
{
    [CmdletBinding()]
    [Alias("Flush-AIClient")]
    [Alias("Flush-AISession")]  # Depricated

    Param
    (
        #The AppInsights Client object to use.
        [Parameter(Mandatory=$false)]
        [Microsoft.ApplicationInsights.TelemetryClient] $Client = $Global:AISingleton.Client
    )
    $client.Flush()
}

<#
.Synopsis
    Stop and flush the App Insights telemetry client, and disables the per-process config.
.EXAMPLE
    Stop-AIClient
.EXAMPLE
    Stop-AIClient -Client $TelemetryClient
#>

function Stop-AIClient
{
    [CmdletBinding()]
    [OutputType([void])]
    Param
    (
        # The Telemetry client to flush and stop, defaults to the
        [Parameter(Mandatory=$false)]
        $Client = $Global:AISingleton.Client
    )
    if ($Client) {
        Write-Verbose "Stopping telemetry client"
        Flush-AIClient -Client $Client
        #And disable telemetry for
        [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.DisableTelemetry = $true
    } else {
        Write-Warning "No AppInsights telemetry client active"
    }
}