newrelic / nri-flex

An application-agnostic, all-in-one New Relic integration integration
https://docs.newrelic.com/docs/introduction-new-relics-flex-integration
Apache License 2.0
109 stars 119 forks source link

Not possible to add user/pass as global to invoke PowerShell script with specific credentials #448

Closed LLHogia closed 10 months ago

LLHogia commented 11 months ago

Description

I have tried the below YML to execute a PowerShell script with a specific user and password:

integrations:
  - name: nri-flex
    config:
      name: TEST_winPerfMon
      global:
        user: testdomain\MonitorUser
        pass: HIDDEN
      apis:
        - event_type: perfmonCounters
          shell: powershell
          commands:
            - run: "& \"C:/Program Files/New Relic/newrelic-infra/integrations.d/test_windows-perfmon-script.ps1\""

I'm not 100% this is the correct syntax. I have tried putting the globals below "name: nri-flex" as well. Same result!

Error message in New Relic: image

No errors in the newrelic-infra.log file. The integrations returns "Integration health check finished with success".

Expected Behavior

Should be possible to pass a username and password to a PowerShell script to invoke that script with the specified credentials.

Steps to Reproduce

  1. Use the YML provided in the description
  2. Use this PowerShell script: https://github.com/newrelic/nri-flex/blob/master/examples/windows/windows-perfmon-script.ps1
  3. Enable PSRemoting on the target machine you want to fetch performance counters from.
  4. Edit the script (windows-perfmon-script.ps1) to get data from a specific machine. This can be done by adding \\COMPUTERNAME\PerformanceCounterPath to the CounterPath attribut. Or just add the computer name as a parameter to the Get-Counter method, like: (Get-Counter -MaxSamples 1 -Counter $c.CounterPath -ComputerName COMPUTERNAME).CounterSamples

Example 1

$results = @()

$counters = @(
    [ PSCustomObject ]@{ CounterName = "percentProcessorTime"; CounterPath = "\Processor(*)\% Processor Time" }
    [ PSCustomObject ]@{ CounterName = "memoryPercentCommitedBytes"; CounterPath = "\Memory\% Committed Bytes In Use" }
    [ PSCustomObject ]@{ CounterName = "diskWritesPerSecond"; CounterPath = "\PhysicalDisk(*)\Disk Writes/sec" }
)

# Iterate through our target counters and grab our results
foreach( $c in $counters ) {
    $query = (Get-Counter -MaxSamples 1 -Counter $c.CounterPath -ComputerName MRAPP01).CounterSamples
    foreach( $q in $query ) {       
        $item = @(
            [ PSCustomObject ]@{ CounterName = $c.CounterName; CounterPath = $c.CounterPath; CounterInstance = $q.InstanceName; CounterValue = $q.CookedValue }
        )

        $results += $item 
    }
}

$results | ConvertTo-Json

Example 2

$results = @()

$counters = @(
    [ PSCustomObject ]@{ CounterName = "percentProcessorTime"; CounterPath = "\\COMPUTERNAME\Processor(*)\% Processor Time" }
    [ PSCustomObject ]@{ CounterName = "memoryPercentCommitedBytes"; CounterPath = "\\COMPUTERNAME\Memory\% Committed Bytes In Use" }
    [ PSCustomObject ]@{ CounterName = "diskWritesPerSecond"; CounterPath = "\\COMPUTERNAME\PhysicalDisk(*)\Disk Writes/sec" }
)

# Iterate through our target counters and grab our results
foreach( $c in $counters ) {
    $query = (Get-Counter -MaxSamples 1 -Counter $c.CounterPath).CounterSamples
    foreach( $q in $query ) {       
        $item = @(
            [ PSCustomObject ]@{ CounterName = $c.CounterName; CounterPath = $c.CounterPath; CounterInstance = $q.InstanceName; CounterValue = $q.CookedValue }
        )

        $results += $item 
    }
}

$results | ConvertTo-Json

Workaround

I want to be able to get performance counters from different machines. This is because we're using a Windows Failover Cluster. So we don't work with unique server names. All performance counters should be targeted via the cluster node name.

  1. Enable PSRemoting on each machine within the cluster.

  2. Create a TXT file with the below examples:

    CLUSTERROLENAME2,\pti.a.RDA_PTPERF.roi(RDA_PTPERF)\FrequencyOfReceivedMessages
    CLUSTERROLENAME1,\UDPv4\Datagrams Received/sec
  3. Create a standard YML file for the integrations and call the PowerShell script. You can use the YML from the description (remove global).

  4. Create the PowerShell script you're calling from the YML file. Since New Relic Infrastructure Agent is running as Local System we need to invoke a command with other credentials. This is why I want to be able to pass the username and password via the YML file so I don't need to invoke from the PowerShell script. See our example below:

    
    $username = 'testdomain\MonitorUser'
    $password = 'HIDDEN'

Construct the PSCredential object

$securePassword = ConvertTo-SecureString $password -AsPlainText -Force $credential = New-Object System.Management.Automation.PSCredential $username, $securePassword

Define the path to the folder containing TXT files

$folderPath = "C:\Program Files\New Relic\newrelic-infra\integrations.d\Performance Counters"

Get all TXT files within the folder

$txtFiles = Get-ChildItem $folderPath -Filter *.txt

Initialize the result array

$results = @()

Loop through each TXT file

foreach ($txtFile in $txtFiles) {

Read the content of the file and split it into lines

$counterList = Get-Content $txtFile.FullName

# Loop through each line in the file
foreach ($line in $counterList) {
    # Split the line into hostname and counter path
    $hostname, $counterPath = $line -split ','

    # Construct the ScriptBlock with the dynamic counter path
    $scriptBlock = {
        param (
            [string]$counterPath
        )
        (Get-Counter -Counter $counterPath).CounterSamples[0].RawValue
    }

    # Invoke the command with the dynamic values
    $result = Invoke-Command -ComputerName $hostname -ScriptBlock $scriptBlock -Credential $credential -ArgumentList $counterPath

    # Append the result to the results array
    $results += [PSCustomObject]@{
        CounterCluster = $hostname
        CounterPath = $counterPath
        CounterValue = $result[0]
        CounterFileName = $txtFile.Name  # Add the filename to the output
    }
}

}

Convert the results array to JSON

$results | ConvertTo-Json



## Your Environment
**Operating System:** Windows Server 2019 OS Build 17763.4851
**New Relic Infrastructure Agent:** 1.47.2

## Additional context
N/A
workato-integration[bot] commented 11 months ago

https://new-relic.atlassian.net/browse/NR-170677

LLHogia commented 11 months ago

One solution could be to add native support for performance counters in YML.

Something like this:

integrations:
  - name: nri-flex
    interval: 120s
    config:
      name: getPerformanceCounter
      global:
        user: WindowsOrDomainUserWithAdminAccess
        pass: password123
      apis:
        - event_type: getPerformanceCounter
          performance_counters:
            - \\ServerA\UDPv4\Datagrams No Port/sec
            - \\ServerB\UDPv4\Datagrams Sent/sec

Or something like this:

integrations:
  - name: nri-flex
    interval: 120s
    config:
      name: getPerformanceCounter
      global:
        user: WindowsOrDomainUserWithAdminAccess
        pass: password123
      apis:
        - event_type: getPerformanceCounter
          performance_counters:
            - name: Datagrams No Port/sec
              counter: \\ServerA\UDPv4\Datagrams No Port/sec

            - name: Datagrams Sent/sec
              counter: \\ServerB\UDPv4\Datagrams Sent/sec

The problem with my workaround is the execution time. Getting 20 counters from different machines takes 15-20 seconds. I assume this is a lot faster if not doing it via an external PowerShell file which also invokes a command as another user.

josemore commented 10 months ago

There's no plan to include perfcounters in Flex. An alternative is the OTel collector receiver: https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/receiver/windowsperfcountersreceiver that can be used to push metrics directly to the OTLP endpoint.