Loosely Decoupled
GuideAWSAutomation

How to Publish a Custom CloudWatch Metric from PowerShell

Send data from your PowerShell scripts to CloudWatch to graph and alarm on, with one cmdlet that runs on Windows, macOS or Linux.

A stipple engraving of a relaxed man in a hoodie dropping a slip of paper into a brass tube, with a ledger chart on the wall behind him. The slip is cyan.
On this page

TL;DR

PowerShell
# Install once per machine; later sessions only need Import-Module.
Install-Module -Name AWS.Tools.CloudWatch -Scope CurrentUser -Force
Import-Module AWS.Tools.CloudWatch

$datum = New-Object Amazon.CloudWatch.Model.MetricDatum
$datum.MetricName = 'QueueDepth'
$datum.Value      = 42
$datum.Unit       = 'Count'
$datum.Timestamp  = (Get-Date).ToUniversalTime()

$hostDimension = New-Object Amazon.CloudWatch.Model.Dimension
$hostDimension.Name  = 'Host'
$hostDimension.Value = [Environment]::MachineName
$datum.Dimensions = @($hostDimension)

Write-CWMetricData -Namespace 'MyApp/Queue' -MetricData $datum

The metric appears under Metrics > All metrics > MyApp/Queue in the CloudWatch console once CloudWatch has indexed it, which can take some minutes. The namespace is yours to choose; don’t begin it with AWS/, which AWS services use.

A custom metric is any number you care about, sent to CloudWatch so you can graph it and alarm on it like a built-in metric: a queue depth, the number of files waiting in a drop folder, or the age of the newest backup. In PowerShell, you put the number in a MetricDatum object and pass it to Write-CWMetricData.

Install the CloudWatch module

AWS.Tools ships one module per AWS service, so you install only the CloudWatch one. It pulls in AWS.Tools.Common on its own:

PowerShell
Install-Module -Name AWS.Tools.CloudWatch -Scope CurrentUser -Force
Import-Module AWS.Tools.CloudWatch

When installing from PSGallery, -Force skips the “Untrusted repository” prompt.

The AWS Tools look for credentials in a profile, in environment variables, or in the instance’s role when the script runs on EC2. Whichever identity they find needs permission for cloudwatch:PutMetricData, the API call behind Write-CWMetricData. CloudWatch is regional, so the tools also need a region: from the profile, the AWS_REGION environment variable, Set-DefaultAWSRegion, or -Region on the cmdlet itself.

Build the MetricDatum

A MetricDatum holds one data point. MetricName names it and Value is the number; Unit and Timestamp are optional but worth setting:

PowerShell
$datum = New-Object Amazon.CloudWatch.Model.MetricDatum
$datum.MetricName = 'QueueDepth'
$datum.Value      = 42
$datum.Unit       = 'Count'
$datum.Timestamp  = (Get-Date).ToUniversalTime()

Unit takes one of a fixed list of names, such as Count, Seconds, Bytes or Percent; MetricDatum in the CloudWatch API Reference (opens in a new tab) lists them all. PowerShell doesn’t check the name when you assign it, so a misspelled unit fails only when Write-CWMetricData sends it. Pick the unit the number actually is. CloudWatch never converts between units, and a metric sent as Count one day and None the next becomes two separate series.

Leave Timestamp off and CloudWatch uses the time the data arrives. Set it when you measured the number earlier than you send it.

Add a dimension

A dimension is a name and a value that describe where the number came from. Without one, every host that sends QueueDepth adds to the same series. With a Host dimension, each host gets its own series. CloudWatch doesn’t add those series together for you; to see all hosts at once, sum them with metric math, or send each datum a second time without the dimension.

PowerShell
$hostDimension = New-Object Amazon.CloudWatch.Model.Dimension
$hostDimension.Name  = 'Host'
$hostDimension.Value = [Environment]::MachineName
$datum.Dimensions = @($hostDimension)

Use [Environment]::MachineName rather than $env:COMPUTERNAME, which is set only on Windows. On macOS or Linux it’s empty, and CloudWatch rejects an empty dimension value.

Keep dimensions few. CloudWatch bills each distinct combination of dimension values as its own metric. A dimension whose value changes every time, such as a request ID or a timestamp, creates a new metric on every run. CloudWatch pricing (opens in a new tab) shows what each custom metric costs.

Send it with Write-CWMetricData

PowerShell
Write-CWMetricData -Namespace 'MyApp/Queue' -MetricData $datum

-MetricData takes an array, so a script that measures several things can send them in one call:

PowerShell
Write-CWMetricData -Namespace 'MyApp/Queue' -MetricData @($depth, $oldestAge, $workers)

One call can carry only so many data points and dimensions; PutMetricData in the CloudWatch API Reference (opens in a new tab) has the current limits.

The cmdlet prints nothing when it succeeds and throws an error when it fails. In a script that runs unattended, wrap it in try/catch; otherwise the script carries on as if the number had been recorded.

Expected result

In the CloudWatch console, open Metrics > All metrics. The MyApp/Queue namespace is listed, and inside it a Host group with one row for each host that has sent data. To check from PowerShell instead:

PowerShell
Get-CWMetricList -Namespace 'MyApp/Queue' | Select-Object MetricName, Dimensions

A new metric can take a few minutes to appear in that list while CloudWatch indexes it; the data was still received. Get-CWMetricData can read the data points before the metric is listed.

Why this works

Write-CWMetricData calls the CloudWatch PutMetricData API. CloudWatch files each data point under its namespace, metric name and set of dimensions, and those three together identify one series.

Standard metrics are stored at one-minute resolution. A number sent once a minute comes back as that number. Numbers sent more often, say every five seconds, are combined into that minute’s sum, count, minimum and maximum. To keep them separate, set StorageResolution to 1 for a high-resolution metric. That costs the same per metric as a standard one; what costs more is the extra calls and any alarm that checks more often than once a minute.

Common failures

Import-Module can’t find AWS.Tools.CloudWatch

Error

Import-Module: The specified module 'AWS.Tools.CloudWatch' was not loaded because no valid module file was found in any module directory.

The CloudWatch module isn’t installed for your account. Installing AWS.Tools.Installer doesn’t install it:

PowerShell
Install-Module -Name AWS.Tools.CloudWatch -Scope CurrentUser -Force

New-Object can’t find the MetricDatum type

Error

New-Object: Cannot find type [Amazon.CloudWatch.Model.MetricDatum]: verify that the assembly containing this type is loaded.
InvalidOperation: The property 'MetricName' cannot be found on this object. Verify that the property exists and can be set.

The module is installed but hasn’t been imported yet. PowerShell imports a module on its own when you call one of its cmdlets, but not when you use one of its types, and New-Object comes first. Every property error after it has the same cause: $datum is empty. Import the module before anything else:

PowerShell
Import-Module AWS.Tools.CloudWatch

Write-CWMetricData prints nothing

No output means it worked. The cmdlet has no output on success and throws an error on failure.

Metric not showing up

Give it a few minutes. A new metric takes a while to appear in the list, and a data point with a timestamp in the past takes longer still. CloudWatch also accepts timestamps only within a window around the current time. PutMetricData in the CloudWatch API Reference (opens in a new tab) gives the current delays and the window.

Graph not updating, or flat at the wrong value

The console graphs the Average over a period longer than a minute. A metric sent once a minute shows the average of several values, so one new value barely moves the line. Set the period to one minute, or switch the statistic to Maximum or Sum, whichever is the number you want.

Namespace keeps filling with new metrics

A dimension value changes on every run. Look at the dimension values under the namespace: if the list grows with each run, that dimension is an identifier, not a category. Put it in a log line instead.

No credentials

Error

Failed to resolve AWS credentials. The credential providers used to search for credentials returned the following errors:

PowerShell found no profile, environment variables or instance role, so the call never reached CloudWatch. Choose a profile for the session, or set the AWS_PROFILE environment variable:

PowerShell
Set-AWSCredential -ProfileName <name>

No region

Error

No region specified or obtained from persisted/shell defaults.

The credentials were found but no region was. Set one for the session, set AWS_REGION, or add -Region <region> to Write-CWMetricData:

PowerShell
Set-DefaultAWSRegion -Region <region>

Not authorized to call PutMetricData

Error

Write-CWMetricData: User: arn:aws:sts::<account-id>:assumed-role/<role>/<session> is not authorized to perform: cloudwatch:PutMetricData because no identity-based policy allows the cloudwatch:PutMetricData action

The credentials work, but the identity named in the message isn’t allowed to call cloudwatch:PutMetricData. To see that identity, install the AWS.Tools.SecurityToken module the same way as the CloudWatch one and run:

PowerShell
Get-STSCallerIdentity

New articles, by email.

A short note when something new is published.