Skip to content

Instantly share code, notes, and snippets.

@PanosGreg
Last active February 11, 2025 18:58
Show Gist options
  • Save PanosGreg/c621a041d561a07f04946a1fffba2bd3 to your computer and use it in GitHub Desktop.
Save PanosGreg/c621a041d561a07f04946a1fffba2bd3 to your computer and use it in GitHub Desktop.
New Relic functions for Synthetic Monitors (enable/disable, get) and API Keys (get)
# change this Enum with your actual accounts and their IDs
enum NewRelicAccountName {
NRAcc1 = 300000
NRAcc2 = 400000
NRAcc3 = 500000
NRAcc4 = 600000
}
function Get-NRMonitor {
<#
.SYNOPSIS
Get Synthetic Monitors from New Relic, using GraphQL
.DESCRIPTION
To get all the monitors using GraphQL, we need to use paging
which is done by using the the NextCursor property
Unfortunately, unlike the REST API, there is no option to have an offset and limit properties.
Which means, the web requests must be done serially, they cannot be done in parallel.
So each time we send the web request, we receive a new cursor and we use that to get the next batch of items.
.EXAMPLE
$key = 'NRAK-123...'
$all = Get-NRMonitor -ApiKey $key
get all the monitors from New Relic
.EXAMPLE
$key = 'NRAK-123...'
$mon = Get-NRMonitor -ApiKey $key -Account NRAcc1 -SearchFor MyApp -Verbose
get only the monitors for a specific account that have a specific string in their name
.EXAMPLE
$key = 'NRAK-123...'
$mon = Get-NRMonitor -ApiKey $key -Account NRAcc1,NRAcc3
get all the monitors from two accounts
#>
[cmdletbinding(DefaultParameterSetName = 'FilterLike')]
param (
[Parameter(Mandatory)]
[ValidatePattern('^NRAK-[A-Z|0-9]{27}$')]
[string]$ApiKey,
[NewRelicAccountName[]]$Account, # <-- optional, default is all accounts
[Parameter(ParameterSetName = 'FilterLike')]
[ValidateNotNullOrWhiteSpace()]
[string]$SearchFor,
[Parameter(ParameterSetName = 'FilterEqual')]
[ValidateNotNullOrWhiteSpace()]
[string]$SearchExactlyFor
)
# helper function
function Convert-NREntityObject($InputObject) {
$InputObject.data.actor.entitySearch.results.entities | foreach {
$Tags = @{}
$_.tags | foreach {$Tags[$_.key] = $_.values | foreach {$_}}
[pscustomobject] @{
PSTypeName = 'NewRelic.SyntheticMonitor'
EntityID = $_.guid
ID = $_.monitorId
Name = $_.name
URL = $_.monitoredUrl
Status = $_.monitorSummary.status
Account = @{Name=$_.account.name;ID=$_.account.id}
Tags = $Tags
}
}
}
# set some default values for Invoke-RestMethod
$PSDefaultParameterValues = @{
'Invoke-RestMethod:Uri' = 'https://api.newrelic.com/graphql'
'Invoke-RestMethod:Method' = 'POST'
'Invoke-RestMethod:Headers' = @{'API-Key' = $ApiKey}
'Invoke-RestMethod:ContentType' = 'application/json'
'Invoke-RestMethod:Verbose' = $false
'Invoke-RestMethod:TimeoutSec' = 5 # <-- this is the ConnectionTimeoutSeconds on PS v7+
'Invoke-RestMethod:UseBasicParsing' = $true # <-- has no effect on PS v6+
'Invoke-RestMethod:ErrorAction' = 'Stop'
}
$WithoutCursor = @'
query GetMonitors($Monitors: String!) {
actor {
entitySearch(query: $Monitors) {
query
count
results {
nextCursor
entities {
... on SyntheticMonitorEntityOutline {
name
guid
monitorSummary {status}
account {id, name}
monitoredUrl
monitorId
tags {key, values}
}
}
}
}
}
}
'@
$WithCursor = @'
query GetMonitors($Monitors: String!) {
actor {
entitySearch(query: $Monitors) {
results(cursor: "@CURSOR@") {
nextCursor
entities {
... on SyntheticMonitorEntityOutline {
name
guid
monitorSummary {status}
account {id, name}
monitoredUrl
monitorId
tags {key, values}
}
}
}
}
}
}
'@
$BaseQuery = [System.Text.StringBuilder]::new("domain = 'SYNTH' AND type = 'MONITOR'")
if ($Account.Count -eq 1) {[void]$BaseQuery.Append(" AND accountId = $($Account.value__)")}
elseif ($Account.Count -ge 2) {[void]$BaseQuery.Append(" AND accountId in ($($Account.value__ -join ','))")}
if ($PSBoundParameters.ContainsKey('SearchFor')) {[void]$BaseQuery.Append(" AND name like '$SearchFor'")}
elseif ($PSBoundParameters.ContainsKey('SearchExactlyFor')) {[void]$BaseQuery.Append(" AND name = '$SearchExactlyFor'")}
$Body = @{query = '' ; variables = @{Monitors = $BaseQuery.ToString()}}
# inital query, where we'll get the 1st cursor
$Body['query'] = $WithoutCursor
$response = Invoke-RestMethod -Body ($Body | ConvertTo-Json) -EA Stop
$Cursor = $response.data.actor.entitySearch.results.nextCursor
$Count = $response.data.actor.entitySearch.count
$First = Convert-NREntityObject -InputObject $response
$ReqCount = [math]::Ceiling($Count/200)
Write-Verbose "Query: $($response.data.actor.entitySearch.query)"
Write-Verbose "Found: $Count monitors (will need to send $ReqCount web calls)"
# now iterate through all the remaining queries until we get them all
$i = 1 # <-- I start from 1 and not 0, cause we already sent 1 web request above
$Results = while ($Cursor -ne $null) {
$i++
$pc = [math]::Round(($i/$ReqCount)*100,0)
if ($ReqCount -ge 4) { # <-- show the progress bar only if we'll be doing 3 or more extra web requests (4 = 1 initial + 3 extra)
Write-Progress -Activity 'Collect New Relic Monitors' -Status "$pc% Complete:" -PercentComplete $pc
}
$Body['query'] = $WithCursor.Replace('@CURSOR@',$Cursor)
$response = Invoke-RestMethod -Body ($Body | ConvertTo-Json)
# refresh the cursor with the next one
$Cursor = $response.data.actor.entitySearch.results.nextCursor
# output the results
Convert-NREntityObject -InputObject $response
} #while
$CollectedCount = $First.Count + $Results.Count
if ($CollectedCount -ne $Count) {Write-Warning "Got $CollectedCount monitors instead of $Count"}
if ([bool]$Results) {Write-Output ($First + $Results)}
else {Write-Output $First}
}
function Switch-NRMonitor {
<#
.SYNOPSIS
It enables or disables one or more monitors (New Relic Synthetic Monitors)
This function uses a REST API endpoint, not a GraphQL mutation (via NerdGraph)
.DESCRIPTION
This function will terminate if there is any error.
So for example if you have 5 IDs and the 3rd one was not found,
then the function will exit and the 4th and 5th won't be executed.
This function is idempotent, so if a monitor is already Enabled and you try to
enable it, the function will identify that and will not do anything.
.EXAMPLE
$Key = 'NRAK-123...'
$IDs = 'e30fe1b8-46cc-4b9c-b7d0-df5a29ffb06a','c66e1635-c303-40c3-9fc6-7b5ad19958a1'
Enable-NRMonitor -MonitorID $IDs -ApiKey $Key -Verbose -PassThru
# enable two monitors
.NOTES
Documentation sources:
- REST API Endpoint URL
https://docs.newrelic.com/docs/apis/synthetics-rest-api/monitor-examples/manage-synthetics-monitors-rest-api/#use-api
- Get a specific monitor
https://docs.newrelic.com/docs/apis/synthetics-rest-api/monitor-examples/manage-synthetics-monitors-rest-api/#get-specific-monitor
- Update individual attribute(s) of synthetic monitors
https://docs.newrelic.com/docs/apis/synthetics-rest-api/monitor-examples/manage-synthetics-monitors-rest-api/#patch-monitor
#>
[Alias('Enable-NRMonitor')]
[Alias('Disable-NRMonitor')]
[OutputType([void],[psobject])] # <-- default is [void], and [psobject] with PassThru
[CmdletBinding()]
param (
[Parameter(Mandatory)]
[guid[]]$MonitorID,
[Parameter(Mandatory)]
[ValidatePattern('^NRAK-[A-Z|0-9]{27}$')]
[string]$ApiKey,
[ValidateSet('US','EU')]
[string]$EndpointRegion = 'US',
[switch]$PassThru
)
trap {
Write-Warning 'There was an error with the web request to New Relic!'
Write-Warning @'
Here are some possible scenarios on what might be happening:
- [404] Not Found = No monitor found for that ID. Make sure your API Key is for the same account as the monitor.
Make sure the monitor ID is correct. Make sure the REST API Endpoint (US or EU) is correct.
- [403] Forbidden = Same as access denied. Make sure you have permissions to change the monitor.
- [400] Bad Request = There might be a policy that does not allow specific values on the attributes of the monitor.
Alternatively, invalid BASE64 encoded string, maybe try again.
- [429] Too many requests = You may have hit the limit of 3 requests per second on the synthetic monitor API.
You may have hit the limit of requests per seconds on the account level.
- [405] Method not allowed = Invalid HTTP Method (ex.GET,POST,PATCH).
There might be a policy that allows only specific methods.
- [504] Timeout = The server side from New Relic did not respond within a timely manner.
- [500] Error = Generic server error from New Relic side.
'@
break # <-- will execute the trap and then show the error from the statement, and then exit the function
}
# find which operation we'll do
$Verb = ($MyInvocation.InvocationName).Split('-')[0]
switch ($Verb) {
'Enable' {$NewStatus = 'ENABLED'}
'Disable' {$NewStatus = 'DISABLED'}
default {Write-Warning 'Please use the alias of this command, for either Enable or Disable';return}
}
# set some default values for Invoke-RestMethod
$PSDefaultParameterValues['Invoke-RestMethod:Headers'] = @{'API-Key' = $ApiKey}
$PSDefaultParameterValues['Invoke-RestMethod:ContentType'] = 'application/json'
$PSDefaultParameterValues['Invoke-RestMethod:Verbose'] = $false
$PSDefaultParameterValues['Invoke-RestMethod:TimeoutSec'] = 5 # <-- this is the ConnectionTimeoutSeconds on PS v7+
$PSDefaultParameterValues['Invoke-RestMethod:UseBasicParsing'] = $true # <-- has no effect on PS v6+
$PSDefaultParameterValues['Invoke-RestMethod:ErrorAction'] = 'Stop'
# get the base URL depending on the region
if ($EndpointRegion -eq 'US') {$BaseUrl = 'https://synthetics.newrelic.com/synthetics/api'}
elseif ($EndpointRegion -eq 'EU') {$BaseUrl = 'https://synthetics.eu.newrelic.com/synthetics/api'}
# loop through each monitor
$out = foreach ($ID in $MonitorID) {
$Url = "$BaseUrl/v3/monitors/$ID"
# first make sure you can get the monitor
$Monitor = Invoke-RestMethod -Uri $Url -Method Get
# get the name of the monitor (sometimes the URI might be empty, not sure why)
if ($Monitor.uri) {$Name = $Monitor.uri}
else {$Name = $Monitor.name}
# now flip the status
if ($Monitor.status -ne $NewStatus) {
Write-Verbose "Change the status for monitor $Name to $NewStatus"
Invoke-RestMethod -Uri $Url -Method Patch -Body "{`"status`": `"$NewStatus`"}" | Out-Null
# and then refresh the monitor variable
$Monitor = Invoke-RestMethod -Uri $Url -Method Get
}
else {
Write-Verbose "The monitor for $Name is already $NewStatus"
}
# make sure the monitor was flipped
if ($Monitor.status -ne $NewStatus) {
throw "The status monitor for $Name was NOT changed to $NewStatus"
}
Write-Output $Monitor
} #foreach monitor
# return the object(s) if PassThru was used
if ($PassThru) {Write-Output $out}
}
function Get-NRApiKey {
<#
.SYNOPSIS
Get all the API Keys and their associated accounts for the user of the provided API Key
.EXAMPLE
$key = 'NRAK-123...'
$all = Get-NRApiKey -ApiKey $key -Verbose
get all the keys from New Relic for the user of the provided API Key
#>
[outputtype([psobject])]
[cmdletbinding()]
param (
[Parameter(Mandatory)]
[ValidatePattern('^NRAK-[A-Z|0-9]{27}$')]
[string]$ApiKey
)
$PSDefaultParameterValues = @{
'Invoke-RestMethod:Uri' = 'https://api.newrelic.com/graphql'
'Invoke-RestMethod:Method' = 'POST'
'Invoke-RestMethod:Headers' = @{'API-Key' = $ApiKey}
'Invoke-RestMethod:ContentType' = 'application/json'
'Invoke-RestMethod:Verbose' = $false
'Invoke-RestMethod:TimeoutSec' = 5 # <-- this is the ConnectionTimeoutSeconds on PS v7+
'Invoke-RestMethod:UseBasicParsing' = $true # <-- has no effect on PS v6+
'Invoke-RestMethod:ErrorAction' = 'Stop'
}
# helper function
function ConvertFrom-UnixTime {
[OutputType([DateTime])]
Param(
[Parameter(Mandatory,ValueFromPipeline=$true)]
[int64]$Ticks # <-- UNIX time long number
)
$Utc = [System.DateTimeKind]::Utc
[DateTime]::new(1970, 1, 1, 0, 0, 0, 0, $Utc).AddSeconds($Ticks).ToLocalTime()
}
# first get your User ID
Write-Verbose 'Get the User ID of the current user'
$UserInfoQuery = @'
{
actor {
user {
name
email
id
}
}
}
'@
$Body = @{query = $UserInfoQuery} | ConvertTo-Json
$User = (Invoke-RestMethod -Body $Body).data.actor.user
# then you need to find the API keys for your user (using your User ID)
# I assume that your key is a USER key and not INGEST
Write-Verbose 'Get the API Key IDs for all the user''s keys'
$KeySearchQuery = @'
{
actor {
apiAccess {
keySearch(query: {scope: {userIds: @USERID@}, types: USER}) {
keys {
id
name
}
}
}
}
}
'@
$Body = @{query = $KeySearchQuery.Replace('@USERID@',$User.id)} | ConvertTo-Json
$KeyIDs = (Invoke-RestMethod -Body $Body).data.actor.apiAccess.keySearch.keys
# finally you can now find your API keys and their associated accounts
Write-Verbose "Get the details for $($KeyIDs.Count) keys"
$ApiKeyQuery = @'
{
actor {
apiAccess {
key(
id: "@KEYID@"
keyType: USER
) {
... on ApiAccessUserKey {
id
name
type
key
createdAt
account {
name
id
}
user {
email
id
name
}
}
}
}
}
}
'@
$i = 0
$KeyInfo = foreach ($KeyObj in $KeyIDs) {
$i++
$pc = [math]::Round(($i/$KeyIDs.Count)*100,0) # <-- pc means percent
if ($KeyIDs.Count -ge 2) { # <-- show the progress bar only if we'll be doing 2 or more web requests
Write-Progress -Activity 'Collect New Relic API Keys' -Status "$pc% Complete:" -PercentComplete $pc
}
$Body = @{query = $ApiKeyQuery.Replace('@KEYID@',$KeyObj.id)} | ConvertTo-Json
(Invoke-RestMethod -Body $Body).data.actor.apiAccess.key
}
$List = foreach ($k in $KeyInfo) {
[pscustomobject] @{
PSTypeName = 'NewRelic.ApiKey'
Name = $k.name
ID = $k.id
Account = $k.account.name
AccountID = $k.account.id -as [int64]
KeyType = $k.type
CreatedAt = ConvertFrom-UnixTime -Ticks $k.createdAt
Key = $k.Key | ConvertTo-SecureString -AsPlainText -Force
UserID = $k.user.id
UserName = $k.user.name
UserEmail = $k.user.email
}
}
# add a default view for the output object
$PSet = [Management.Automation.PSPropertySet]
$Prop = 'Name','Account','UserName','CreatedAt'
$DDPS = $PSet::new('DefaultDisplayPropertySet',[string[]]$Prop)
$Std = [Management.Automation.PSMemberInfo[]]@($DDPS)
$List | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $Std -Force
Write-Output $List
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment