PSCoreApplicationInsights.psm1

function New-ApplicationInsightsClient {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true, HelpMessage = "The Application Insights Instrumentation Key that is used to send the messages to the correct Application Insights Instance.")]
        [ValidateNotNullOrEmpty()]
        [Guid]
        $InstrumentationKey
    )

    $global:AIClient = [Microsoft.ApplicationInsights.TelemetryClient]::new()
    $global:AIClient.InstrumentationKey = $InstrumentationKey

    $defaultUserInformation = @{
        AuthenticatedUserId = whoami
        UserAgent           = ("PS $($psversiontable.PSEdition) $($psversiontable.PSVersion)")
    }

    $defaultDeviceInformation = @{
        OperatingSystem = $psversiontable.OS

    }

    $global:AIClient = Set-ApplicationInsightsClientInformation -UserInformation $defaultUserInformation -DeviceInformation $defaultDeviceInformation -Client $global:AIClient

    return $global:AIClient
}

Export-ModuleMember -Function New-ApplicationInsightsClient
function Confirm-ApplicationInsightsClient {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client
    )

    if ($null -eq $client) {
        if ($null -eq $global:AIClient) {
            write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
            return;
        } else {
            $client = $global:AIClient
        }
    }


    [bool] $isValid = $true
    Write-Verbose ("Checking if the Application Insights Client is valid...")

    if ([string]::IsNullOrWhiteSpace($client.InstrumentationKey)) {
        Write-Verbose ("The Instrumentation Key is not set.")
        $isValid = $false
    }

    if ($client.Isenabled() -eq $false) {
        Write-Verbose ("The Application Insights Client is not enabled.")
        $isValid = $false
    }

    if ($isValid -eq $true) {
        Write-Verbose ("The Application Insights Client is valid.")
    }
    else {
        throw ("The Application Insights Client is not valid.")
    }
}

function Set-ApplicationInsightsClientInformation {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $false)]
        [hashtable]
        $UserInformation,

        [Parameter(Mandatory = $false)]
        [hashtable]
        $DeviceInformation
    )
    begin {
        if (-not $null -eq $UserInformation) {
            Write-Verbose ("Received 'User' properties to set in the client")
        }

        if (-not $null -eq $DeviceInformation) {
            Write-Verbose ("Received 'Device' properties to set in the client")
        }
    }

    process {
        if (-not $null -eq $UserInformation) {

            foreach ($property in $Client.Context.User.psobject.Properties.name) {
                Write-Verbose ("Checking property '$($property)' in supplied hashtable")
                if (-not [string]::IsNullOrWhiteSpace($UserInformation[$property])) {
                    Write-Verbose ("Found property '$($property)' with a value. Changing value from '$($Client.Context.User.$property)' to '$($UserInformation[$property])'")

                    $Client.Context.User.$property = $UserInformation[$property]
                }
            }
        }

        if (-not $null -eq $DeviceInformation) {

            foreach ($property in $Client.Context.Device.psobject.Properties.name) {
                Write-Verbose ("Checking property '$($property)' in supplied hashtable")
                if (-not [string]::IsNullOrWhiteSpace($DeviceInformation[$property])) {
                    Write-Verbose ("Found property '$($property)' with a value. Changing value from '$($Client.Context.Device.$property)' to '$($DeviceInformation[$property])'")

                    $Client.Context.Device.$property = $DeviceInformation[$property]
                }
            }
        }
    }

    end {
        return $Client
    }

}

Export-ModuleMember -Function Set-ApplicationInsightsClientInformation

function Write-ApplicationInsightsTrace {
    <#
    .SYNOPSIS
    Write a simple Trace message to the Application Insights service.
 
    .DESCRIPTION
    Write a simple Trace message to the Application Insights service. Supports several Seveity levels
 
    .PARAMETER Client
    The Application Insights client you want to use to send the message to Application Insights.
 
    .PARAMETER Message
    The message you want to send to Application Insights.
 
    .PARAMETER SeverityLevel
    The severity level of the message. Default is 'Information'.
 
    Allowed values: 'Verbose', 'Information', 'Warning', 'Error', 'Critical'
 
    .PARAMETER properties
    A Dictionary of properties you want to send with the message.
 
    .EXAMPLE
    Write-ApplicationInsightsTrace -Client $client -Message "This is a test message as Critical" -SeverityLevel "Critical"
 
    .EXAMPLE
 
    $properties = [System.Collections.Generic.Dictionary[string, string]]::new()
 
    $properties.Add("target", "azkv-powershell-001")
    $properties.Add("type", "Keyvault")
 
    Write-ApplicationInsightsTrace -Client $client -Message "Created new keyvault" -SeverityLevel "Information" -properties $properties
 
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $true)]
        [string]
        $Message,

        [Parameter(Mandatory = $false)]
        [validateSet('Information', 'Verbose', 'Warning', 'Error', 'Critical')]
        [string]
        $SeverityLevel = "information",

        [Parameter(Mandatory = $false, HelpMessage = "This is a dictionary<string, string> with additional information that will be added as 'customDimensions' in Application Insights")]
        [System.Collections.Generic.Dictionary[string, string]]
        $properties
    )
    BEGIN {
        Write-Verbose ("Received '$($SeverityLevel)' severity level for the message '$($Message)'")

        if ($null -eq $client) {
            if ($null -eq $global:AIClient) {
                write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
                return;
            } else {
                $client = $global:AIClient
            }
        }

        if ($properties.Count -ge 1) {
            Write-Verbose ("Received '$($properties.Count)' properties to add to the message.")
        }

        Confirm-ApplicationInsightsClient $client
    }
    PROCESS {
        if ($properties.Count -ge 1) {
            $Client.TrackTrace($Message, [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]::$($SeverityLevel), $properties)
            Write-Verbose ("Sent message '$($Message)' with '$($properties.Count)' properties to Application Insights.")
        }
        else {
            $Client.TrackTrace($Message, [Microsoft.ApplicationInsights.DataContracts.SeverityLevel]::$($SeverityLevel))
            Write-Verbose ("Sent message '$($Message)' to Application Insights.")
        }
    }
    END {
        $Client.Flush()
        Write-Verbose ("Client Flushed")
    }
}

Export-ModuleMember -Function Write-ApplicationInsightsTrace

function Write-ApplicationInsightsMetric {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $true)]
        [string]
        $Name,

        [Parameter(Mandatory = $true)]
        [Double]
        $Metric,

        [Parameter(Mandatory = $false, HelpMessage = "This is a dictionary<string, string> with additional information that will be added as 'customDimensions' in Application Insights")]
        [System.Collections.Generic.Dictionary[string, string]]
        $properties
    )
    BEGIN {

        if ($null -eq $client) {
            if ($null -eq $global:AIClient) {
                write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
                return;
            } else {
                $client = $global:AIClient
            }
        }

        if ($properties.Count -ge 1) {
            Write-Verbose ("Received '$($properties.Count)' properties to add to the message.")
        }
    }
    PROCESS {
        if ($properties.Count -ge 1) {
            $client.TrackMetric($name, $Metric, $properties)
            Write-Verbose ("Sent metric '$($Name)' with '$($Metric)' value and '$($properties.Count)' properties to Application Insights.")
        }
        else {
            $client.TrackMetric($name, $Metric)
            Write-Verbose ("Sent metric '$($Name)' with '$($Metric)' value to Application Insights.")
        }

    }
    END {
        $Client.Flush()
        Write-Verbose ("Client Flushed")
    }
}

Export-ModuleMember -Function Write-ApplicationInsightsMetric
function Write-ApplicationInsightsException {
    [CmdletBinding(DefaultParameterSetName = "Exception")]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $true, ParameterSetName = "Exception")]
        [System.Exception]
        $Exception,

        [Parameter(Mandatory = $true, ParameterSetName = "StringException")]
        [String]
        $ExceptionString,

        [Parameter(Mandatory = $false, HelpMessage = "This is a dictionary<string, double> with additional information that will be added as 'customMeasurements' in Application Insights")]
        [System.Collections.Generic.Dictionary[string, double]]
        $Metrics = [System.Collections.Generic.Dictionary[string, double]]::new(),

        [Parameter(Mandatory = $false, HelpMessage = "This is a dictionary<string, string> with additional information that will be added as 'customDimensions' in Application Insights")]
        [System.Collections.Generic.Dictionary[string, string]]
        $properties = [System.Collections.Generic.Dictionary[string, string]]::new()
    )
    BEGIN {
        Write-Verbose ("Running in Parameterset '$($PSCmdlet.ParameterSetName)'")

        if ($null -eq $client) {
            if ($null -eq $global:AIClient) {
                write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
                return;
            } else {
                $client = $global:AIClient
            }
        }

        if ($PSCmdlet.ParameterSetName -eq "StringException") {
            $Exception = [System.Exception]::new($ExceptionString)
        }
    }
    PROCESS {
        $client.TrackException($Exception, $properties, $Metrics)
        Write-Verbose ("Sent exception '$($Exception)' with '$($Metrics.Count)' metrics and '$($properties.Count)' properties to Application Insights.")
    }
    END {
        $Client.Flush()
        Write-Verbose ("Client Flushed")
    }
}

Export-ModuleMember -Function Write-ApplicationInsightsException

function Write-ApplicationInsightsRequest {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [String]
        $Name,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [System.DateTimeOffset]
        $StartTime,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Timespan]
        $Duration,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [String]
        $responseCode = "200",

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [Bool]
        $success,

        [Parameter(Mandatory = $false, HelpMessage = "This is a dictionary<string, string> with additional information that will be added as 'customProperties' in Application Insights")]
        [System.Collections.Generic.Dictionary[string, string]]
        $properties = [System.Collections.Generic.Dictionary[string, string]]::new(),

        [Parameter(Mandatory = $false, HelpMessage = "This is the URL that will be added as 'url' in Application Insights")]
        [ValidateNotNullOrEmpty()]
        [string]
        $url
    )
    BEGIN {
        Write-Verbose ("Received '$($Name)' name for the request")
        Write-Verbose ("Received '$($StartTime)' start time for the request")
        Write-Verbose ("Received '$($Duration)' duration for the request")
        Write-Verbose ("Received '$($responseCode)' response code for the request")
        Write-Verbose ("Received '$($success)' success for the request")

        if ($null -eq $client) {
            if ($null -eq $global:AIClient) {
                write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
                return;
            } else {
                $client = $global:AIClient
            }
        }

    }
    PROCESS {

        $requestTelemetry = [Microsoft.ApplicationInsights.DataContracts.RequestTelemetry]::new()

        $requestTelemetry.Duration = $Duration
        $requestTelemetry.Name = $Name
        $requestTelemetry.ResponseCode = $responseCode
        $requestTelemetry.Success = $success
        $requestTelemetry.Timestamp = $StartTime

        if ($properties.Count -ge 1) {
            Write-Verbose ("Received '$($properties.Count)' properties to add to the request.")
            foreach ($key in $properties.Keys) {
                Write-Verbose ("Received '$($key)' property to add to the request.")
                $requestTelemetry.Properties[$key] = $properties[$key]
            }
        }

        if (-not [string]::IsNullOrWhiteSpace($url)) {
            Write-Verbose ("Received '$($url)' url for the request")
            $requestTelemetry.Url = $url
        }

        $client.TrackRequest($requestTelemetry)

    }
    END {
        $Client.Flush()
        Write-Verbose ("Client Flushed")
    }
}

Export-ModuleMember -Function Write-ApplicationInsightsRequest
Function Invoke-ApplicationInsightsMeasuredCommand {
    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [Microsoft.ApplicationInsights.TelemetryClient]
        $Client,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $scriptblock,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $name
    )
    BEGIN {

        if ($null -eq $client) {
            if ($null -eq $global:AIClient) {
                write-error ("No Application insight client defined. Please use 'New-ApplicationInsightsClient' to create one.")
                return;
            } else {
                $client = $global:AIClient
            }
        }

        Write-Verbose ("Received '$($name)' name for the command")
    }
    PROCESS {

        $success = $true
        $statusCode = "200"

        $startDate = [System.DateTime]::UtcNow

        try {
            $retVal = $scriptblock.Invoke()
        }
        catch [System.Exception] {
            Write-Verbose ("Caught exception in the scriptblock")
            $statusCode = $_
            $success = $false
            throw
        }
        finally {
            $endDate = [System.DateTime]::UtcNow
        }

        $duration = New-TimeSpan -Start $startDate -End $endDate

        Write-Verbose ("Received '$($duration)' as duration for the command")
        Write-ApplicationInsightsRequest -Name $name -StartTime $startDate -Duration $duration -responseCode $statusCode -success $success
    }
    END {
        $Client.Flush()
        Write-Verbose ("Client Flushed")
        return $retVal
    }
}

Export-ModuleMember -Function Invoke-ApplicationInsightsMeasuredCommand