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.

On this page
TL;DR
# 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 $datumThe 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:
Install-Module -Name AWS.Tools.CloudWatch -Scope CurrentUser -Force
Import-Module AWS.Tools.CloudWatchWhen 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:
$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.
$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
Write-CWMetricData -Namespace 'MyApp/Queue' -MetricData $datum-MetricData takes an array, so a script that measures several things can send
them in one call:
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:
Get-CWMetricList -Namespace 'MyApp/Queue' | Select-Object MetricName, DimensionsA 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:
Install-Module -Name AWS.Tools.CloudWatch -Scope CurrentUser -ForceNew-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:
Import-Module AWS.Tools.CloudWatchWrite-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:
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:
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 actionThe 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:
Get-STSCallerIdentity