PSAppInsights.psm1
<#
PowerShell App Insights Module V0.3 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 #> $script:ErrNoClient = "Client - No Application Insights Client specified or initialized." if ($false) { $request = New-Object 'Microsoft.ApplicationInsights.DataContracts.RequestTelemetry' #new RequestTelemetry(); $client = New-AISession -Key $key $request.Name = "My Request"; $request.Context.Properties["User Name"] = "Jos" $request.Context.Properties["Tenant Code"] = "tenantCode" $client.TrackRequest($request) #Also for $event = New-Object Microsoft.ApplicationInsights.DataContracts.EventTelemetry $client.TrackEvent($event) $AIExeption = New-Object Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry $client.TrackEvent($AIExeption) <# [TestMethod] public void SerializeWritesPropertiesAsExpectedByEndpoint() { ExceptionTelemetry expected = CreateExceptionTelemetry(); expected.Properties.Add("TestProperty", "TestValue"); var item = TelemetryItemTestHelper.SerializeDeserializeTelemetryItem<ExceptionTelemetry, DataPlatformModel.ExceptionData>(expected); Assert.Equal(expected.Properties.ToArray(), item.Data.BaseData.Properties.ToArray()); } [TestMethod] public void SerializeWritesMetricsAsExpectedByEndpoint() { ExceptionTelemetry expected = CreateExceptionTelemetry(); expected.Metrics.Add("TestMetric", 4.2); var item = TelemetryItemTestHelper.SerializeDeserializeTelemetryItem<ExceptionTelemetry, DataPlatformModel.ExceptionData>(expected); Assert.Equal(expected.Metrics.ToArray(), item.Data.BaseData.Measurements.ToArray()); } #> } <# .Synopsis Start a new AI Session .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet #> function New-AISession { [CmdletBinding()] [OutputType([Microsoft.ApplicationInsights.TelemetryClient])] Param ( # The Instrumentation Key for Application Analytics [Parameter(Mandatory=$true, ValueFromPipelineByPropertyName=$true, Position=0)] $Key , [string]$SessionID = (New-Guid), [string]$OperationID = (New-Guid), #Version of the application or Component $Version, # Set to indicate messages sent from or during a test [string]$Synthetic = $null, #Allow PII in Traces [switch]$AllowPII ) Process { try { Write-Verbose "create Telemetry client" $client = New-Object Microsoft.ApplicationInsights.TelemetryClient if ($client) { Write-Verbose "Add Key, Session.id and Operation.id" $client.InstrumentationKey = $Key $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" $client.Context.Device.OperatingSystem = (Get-CimInstance Win32_OperatingSystem).version $client.Context.User.UserAgent = $Host.Name if ($AllowPII) { Write-Verbose "Add PII user and computer" #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" #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:AIClient -ne $null ) { Write-Verbose "replacing active telemetry client" Flush-AISession -Client $global:AIClient Remove-Variable AIClient -Scope Global } #Save client in Global for re-use when not specified $global:AIClient = $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 $AIClient.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 Service TODO Add Alias FLUSH ? #> function Push-AISession { [CmdletBinding()] [Alias("Flush-AISession")] Param ( #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient ) $client.Flush() } <# Initializers The Application Insights .NET SDK consists of a number of NuGet packages. The core package provides the API for sending telemetry to the Application Insights. By adjusting the configuration file, you can enable or disable telemetry modules and initializers, and set parameters for some of them. The configuration file is named ApplicationInsights.config or ApplicationInsights.xml, depending on the type of your application. DeviceTelemetryInitializer updates the following properties of the Device context for all telemetry items. - Type is set to "PC" - Id is set to the domain name of the computer where the web application is running. - OemName is set to the value extracted from the Win32_ComputerSystem.Manufacturer field using WMI. - Model is set to the value extracted from the Win32_ComputerSystem.Model field using WMI. - NetworkType is set to the value extracted from the NetworkInterface. - Language is set to the name of the CurrentCulture. #> <# .Synopsis Send a trace message to Application Insights .EXAMPLE Example of how to use this cmdlet #> function Send-AITrace { [CmdletBinding()] #[OutputType([int])] Param ( # The Trace Message [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string] $Message, #Severity, Defaults to Information [Parameter()] [Alias("Severity")] $SeverityLevel = [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]::Information, #any custom Properties that need to be added to the event [Hashtable]$Properties, #include call stack information (Default) [switch] $NoStack, #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient, #Directly flush the AI events to the service [switch] $Flush ) #Check for a specified AI client if ($Client -eq $null) { throw [System.Management.Automation.PSArgumentNullException]::new($script:ErrNoClient) } #Setup dictionaries $dictProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]' #Send the callstack if ($NoStack -eq $false) { $dictProperties = getCallerInfo -level 2 } #Add the Properties to Dictionary if ($Properties) { foreach ($h in $Properties.GetEnumerator() ) { $dictProperties.Add($h.Name, $h.Value) } } $sev = [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]$SeverityLevel $client.TrackTrace($Message, $Sev, $dictProperties) #$client.TrackTrace($Message) if ($Flush) { $client.Flush() } } <# .Synopsis Send a Custom Event to Application Insights. A custom event is a data point that you can display both in in Metrics Explorer as an aggregated count, and also as individual occurrences in Diagnostic Search .EXAMPLE Example of how to use this cmdlet #> function Send-AIEvent { [CmdletBinding()] #[OutputType([int])] Param ( # The Trace Message [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string] $Event, #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient, #any custom Properties that need to be added to the event [Hashtable]$Properties, #any custom metrics that need to be added to the event [Hashtable]$Metrics, #include call stack information (Default) [switch] $NoStack, #Directly flush the AI events to the service [switch] $Flush ) #Check for a specified AI client if ($Client -eq $null) { throw [System.Management.Automation.PSArgumentNullException]::new($script:ErrNoClient) } #Setup dictionaries $dictProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]' $dictMetrics = New-Object 'system.collections.generic.dictionary[[string],[double]]' #Send the callstack if ($NoStack -eq $false) { $dictProperties = getCallerInfo -level 2 } #Add the Properties to Dictionary if ($Properties) { foreach ($h in $Properties.GetEnumerator() ) { $dictProperties.Add($h.Name, $h.Value) } } #Convert metrics to Dictionary if ($Metrics) { foreach ($h in $Metrics.GetEnumerator()) { $dictMetrics.Add($h.Name, $h.Value) } } #Send the event $client.TrackEvent($Message, $dictProperties , $dictMetrics) if ($Flush) { $client.Flush() } } <# .Synopsis name A string that identifies the metric. In the portal, you can select metrics for display by name. average Either a single measurement, or the average of several measurements. Should be >=0 to be correctly displayed. sampleCount Count of measurements represented by the average. Defaults to 1. Should be >=1. min The smallest measurement in the sample. Defaults to the average. Should be >= 0. max The largest measurement in the sample. Defaults to the average. Should be >= 0. properties Map of string to string: Additional data used to filter events in the portal. .EXAMPLE Example of how to use this cmdlet #> function Send-AIMetric { [CmdletBinding()] #[OutputType([int])] Param ( # The Trace Message [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string] $Metric, [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [double] $Value, #any custom Properties that need to be added to the event [Hashtable]$Properties, #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient, #include call stack information (Default) [switch] $NoStack, #Directly flush the AI events to the service [switch] $Flush ) #Check for a specified AI client if ($Client -eq $null) { throw [System.Management.Automation.PSArgumentNullException]::new($script:ErrNoClient) } #Setup dictionaries $dictProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]' #Send the callstack if ($NoStack -eq $false) { $dictProperties = getCallerInfo -level 2 } #Add the Properties to Dictionary if ($Properties) { foreach ($h in $Properties.GetEnumerator() ) { $dictProperties.Add($h.Name, $h.Value) } } $client.trackMetric($Metric, $Value, $dictProperties); if ($Flush) { $client.Flush() } } <# Dependency Tracking Dependency tracking collects telemetry about calls your app makes to databases and external services and databases #> <# .Synopsis #> function Send-AIException { [CmdletBinding()] #[OutputType([int])] Param ( # An Error from a catch clause. [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [System.Exception] $Exception, #Defaults to "unhandled" $HandledAt = $null, #Map of string to string: Additional data used to filter pages in the portal. $Properties = $null, #Map of string to number: Metrics associated with this page, displayed in Metrics Explorer on the portal. $Metrics = $null, #The Severity of the Exception 0 .. 4 : Default = 2 $Severity = 2, #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient, #include call stack information (Default) [switch] $NoStack, #Directly flush the AI events to the service [switch] $Flush ) #Check for a specified AI client if ($Client -eq $null) { throw [System.Management.Automation.PSArgumentNullException]::new($script:ErrNoClient) } #Setup dictionaries # $dictProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]' # $dictMetrics = New-Object 'system.collections.generic.dictionary[[string],[double]]' $AIExeption = New-Object Microsoft.ApplicationInsights.DataContracts.ExceptionTelemetry <# #Send the callstack if ($NoStack -eq $false) { $dictProperties = getCallerInfo -level 2 #? Add the caller info $AIExeption.Properties.Add($dictProperties) } #> #Add the Properties to Dictionary if ($Properties) { foreach ($h in $Properties.GetEnumerator() ) { ($AIExeption.Properties).Add($h.Name, $h.Value) } } #Convert metrics to Dictionary if ($Metrics) { foreach ($h in $Metrics.GetEnumerator()) { ($AIExeption.Metrics).Add($h.Name, $h.Value) } } $AIExeption.Exception = $Exception $client.TrackEvent($AIExeption) #$client.TrackException($Exception) #$client.TrackException($Exception, $HandledAt, $Properties, $Metrics, $Severity) <# exception An Error from a catch clause. handledAt Defaults to "unhandled". properties Map of string to string: Additional data used to filter exceptions in the portal. Defaults to empty. measurements Map of string to number: Metrics associated with this page, displayed in Metrics Explorer on the portal. Defaults to empty. severityLevel Supported values: SeverityLevel.ts SeverityLevel[SeverityLevel["Verbose"] = 0] = "Verbose"; SeverityLevel[SeverityLevel["Information"] = 1] = "Information"; SeverityLevel[SeverityLevel["Warning"] = 2] = "Warning"; SeverityLevel[SeverityLevel["Error"] = 3] = "Error"; SeverityLevel[SeverityLevel["Critical"] = 4] = "Critical"; void TrackException(System.Exception exception, System.Collections.Generic.IDictionary[string,string] properties, System.Collections.Generic.IDictionary[string,double] metrics) #> if ($Flush) { $client.Flush() } } <# .Synopsis Send a Custom Event to Application Insights. A custom event is a data point that you can display both in in Metrics Explorer as an aggregated count, and also as individual occurrences in Diagnostic Search .EXAMPLE Example of how to use this cmdlet #> function Send-AIPageView { [CmdletBinding()] #[OutputType([int])] Param ( # The Name of the Page [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)] [string] $PageName, #The URL of the Page [string] $URL = $null, #Map of string to string: Additional data used to filter pages in the portal. $Properties = $null, #Map of string to number: Metrics associated with this page, displayed in Metrics Explorer on the portal. $Metrics = $null, # Duration In Milliseconds #ToDo / Change to Timeinterval ? [int] $Duration, #The AppInsights Client object to use. [Parameter(Mandatory=$false)] [Microsoft.ApplicationInsights.TelemetryClient] $Client = $global:AIClient, #include call stack information (Default) [switch] $NoStack, #Directly flush the AI events to the service [switch] $Flush ) #Check for a specified AI client if ($Client -eq $null) { throw [System.Management.Automation.PSArgumentNullException]::new($script:ErrNoClient) } #Setup dictionaries $dictProperties = New-Object 'system.collections.generic.dictionary[[string],[string]]' $dictMetrics = New-Object 'system.collections.generic.dictionary[[string],[double]]' #Send the callstack if ($NoStack -eq $false) { $dictProperties = getCallerInfo -level 2 } #Add the Properties to Dictionary if ($Properties) { foreach ($h in $Properties.GetEnumerator() ) { $dictProperties.Add($h.Name, $h.Value) } } #Convert metrics to Dictionary if ($Metrics) { foreach ($h in $Metrics.GetEnumerator()) { $dictMetrics.Add($h.Name, $h.Value) } } #$durationInMilliseconds $client.trackPageView($PageName, $URL , $Properties, $Metrics, $duration); <# void TrackPageView(string name) void TrackPageView(Microsoft.ApplicationInsights.DataContracts.PageViewTelemetry telemetry) #> if ($Flush) { $client.Flush() } } <# .Synopsis Short description .DESCRIPTION Long description .EXAMPLE Example of how to use this cmdlet #> function start-AIPerformanceCollector { [CmdletBinding()] Param( # The App Insights Key [Parameter(Mandatory=$true)] $Key, #Process Counters of the powershell process to collect [string[]] $ProcessCounters = @( 'Handle Count', 'Working Set' ) # @todo Set to suppress sending messages in a test environment # [string]$Synthetic ) if ($Global:AIperfCollector) { Stop-AIPerformanceCollector } Write-Verbose "Create AI Performance Collector Instance" $Global:AIperfCollector = New-Object Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCollectorModule Write-Verbose "Add ??APP_WIN32_PROC?? performance counters" #$ProcessName = "??{0}??" -f ([System.Diagnostics.Process]::GetCurrentProcess()).ProcessName $ProcessName = ([System.Diagnostics.Process]::GetCurrentProcess()).ProcessName foreach ( $c in $ProcessCounters ) { $Counter = "\Process($ProcessName)\$c" $CollectionRequest = New-Object Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCounterCollectionRequest -ArgumentList $Counter, $c $Global:AIperfCollector.Counters.Add( $CollectionRequest) } #Add Processes $CollectionRequest = New-Object Microsoft.ApplicationInsights.Extensibility.PerfCounterCollector.PerformanceCounterCollectionRequest -ArgumentList "\Objects\Processes", "Processes" $Global:AIperfCollector.Counters.Add( $CollectionRequest) <# Debug [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.TelemetryChannel.EndpointAddress = 'http://localhost:8888/v2/track' #> Write-Verbose "Initialize" [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active.InstrumentationKey = $key $Global:AIperfCollector.Initialize( [Microsoft.ApplicationInsights.Extensibility.TelemetryConfiguration]::Active ) } function Stop-AIPerformanceCollector { [CmdletBinding()] Param() if ($Global:AIperfCollector) { Write-Verbose "Stoping Performance counter collection" $Global:AIperfCollector.Dispose() Remove-Variable AIperfCollector -Scope Global } } <#------------------------------------------------------------------------------------------------------------------ Helper Functions --------------------------------------------------------------------------------------------------------------------#> <# helper function Get-StringHash Credits : Jeff Wouters ref: http://jeffwouters.nl/index.php/2013/12/Get-StringHash-for-files-or-strings/ #> function Get-StringHash { [cmdletbinding()] param ( [parameter(mandatory=$false,parametersetname="String")]$String, [parameter(mandatory=$false,parametersetname="File")]$File, [parameter(mandatory=$false,parametersetname="String")] [validateset("MD5","SHA1","SHA256","SHA384","SHA512","RIPEMD160")] [parameter(mandatory=$false,parametersetname="File")] [validateset("MD5","SHA1","SHA256","SHA384","SHA512","RIPEMD160")] [string]$HashType = "MD5" ) switch ($PsCmdlet.ParameterSetName) { "String" { $StringBuilder = New-Object System.Text.StringBuilder [System.Security.Cryptography.HashAlgorithm]::Create($HashType).ComputeHash([System.Text.Encoding]::UTF8.GetBytes($String))| ForEach-Object { [Void]$StringBuilder.Append($_.ToString("x2")) } $Object = New-Object -TypeName PSObject $Object | Add-Member -MemberType NoteProperty -Name 'String' -value $String $Object | Add-Member -MemberType NoteProperty -Name 'HashType' -Value $HashType $Object | Add-Member -MemberType NoteProperty -Name 'Hash' -Value $StringBuilder.ToString() $Object } "File" { $StringBuilder = New-Object System.Text.StringBuilder $InputStream = New-Object System.IO.FileStream($File,[System.IO.FileMode]::Open) switch ($HashType) { "MD5" { $Provider = New-Object System.Security.Cryptography.MD5CryptoServiceProvider } "SHA1" { $Provider = New-Object System.Security.Cryptography.SHA1CryptoServiceProvider } "SHA256" { $Provider = New-Object System.Security.Cryptography.SHA256CryptoServiceProvider } "SHA384" { $Provider = New-Object System.Security.Cryptography.SHA384CryptoServiceProvider } "SHA512" { $Provider = New-Object System.Security.Cryptography.SHA512CryptoServiceProvider } "RIPEMD160" { $Provider = New-Object System.Security.Cryptography.CryptoServiceProvider } } $Provider.ComputeHash($InputStream) | Foreach-Object { [void]$StringBuilder.Append($_.ToString("X2")) } $InputStream.Close() $Object = New-Object -TypeName PSObject $Object | Add-Member -MemberType NoteProperty -Name 'File' -value $File $Object | Add-Member -MemberType NoteProperty -Name 'HashType' -Value $HashType $Object | Add-Member -MemberType NoteProperty -Name 'Hash' -Value $StringBuilder.ToString() $Object } } } <# Helper function to get the script and the line number of the calling function #> function getCallerInfo ($level = 2) { [CmdletBinding()] $dict = New-Object 'system.collections.generic.dictionary[[string],[string]]' try { #Get the caller info $caller = (Get-PSCallStack)[$level] #get only the script name $ScriptName = '<unknown>' if ($caller.Location) { $ScriptName = ($caller.Location).Split(':')[0] } $dict.Add('ScriptName', $ScriptName) $dict.Add('ScriptLineNumber', $caller.ScriptLineNumber) $dict.Add('Command', $caller.Command) $dict.Add('FunctionName', $caller.FunctionName) return $dict } catch { return $null} } <# Helper function to get the calling script or module version#> function getCallerVersion { [CmdletBinding()] param( [int]$level = 2 ) try { #Get the caller info $caller = (Get-PSCallStack)[$level] #get only the script name $ScriptName = '<unknown>' if ($caller.Location) { $ScriptName = ($caller.Location).Split(':')[0] } $dict.Add('ScriptName', $ScriptName) $dict.Add('ScriptLineNumber', $caller.ScriptLineNumber) $dict.Add('Command', $caller.Command) $dict.Add('FunctionName', $caller.FunctionName) return $dict } catch { return $null} } <# Helper function to get the calling script or module version #> function getCallerVersion { [CmdletBinding()] param( #Get version from X levels up in the call stack [int]$level = 1 ) Write-Verbose "getCallerVersion -level $level" [Version]$V = $null try { #Get the caller info $caller = (Get-PSCallStack)[$level] #if script if ( -NOT [string]::IsNullOrEmpty( $caller.ScriptName)){ $info = Test-ScriptFileInfo -Path $caller.ScriptName -ErrorAction SilentlyContinue if ( $info ) { $v = $info.Version Write-Verbose "getCallerVersion found script version $v" return $v } } } catch { } Try { #try module info based on the name, but with a psd1 extention $Filename = [System.IO.Path]::ChangeExtension( $caller.ScriptName, 'psd1') $info = Test-ModuleManifest -Path $Filename -ErrorAction SilentlyContinue if ( $info ) { $v = $info.Version Write-Verbose "getCallerVersion found Module version $v" return $v break; } } catch {} # Continue try { #try to find a version from the path and folder names $Folders= @( $Filename.Split('\') ) $found = $false foreach( $f in $Folders ) { Try { $V = [version]$f ; $found = $true} catch {} } if ($found) { #return last found version Write-Verbose "getCallerVersion found Folder version $v" return $v } } catch { Write-Verbose "getCallerVersion no version found" return $v } Write-Verbose "no version found" return $v } |