diff --git a/examples/aws_cli.py b/examples/aws_cli.py
index 3df2c2d..cf0f704 100644
--- a/examples/aws_cli.py
+++ b/examples/aws_cli.py
@@ -147,3 +147,19 @@ def StartAnalysisVm(args: 'argparse.Namespace') -> None:
   print('Name: {0:s}, Started: {1:s}, Region: {2:s}'.format(vm[0].name,
                                                             str(vm[1]),
                                                             vm[0].region))
+
+def ListImages(args: 'argparse.Namespace') -> None:
+  """List AMI images and filter on AMI image 'name'.
+
+  Args:
+    args (argparse.Namespace): Arguments from ArgumentParser.
+  """
+  aws_account = account.AWSAccount(args.zone)
+
+  qfilter = [{'Name':'name', 'Values':[args.filter]}]
+
+  images = aws_account.ListImages(qfilter)
+
+  for image in images:
+    print('Name: {0:s}, ImageId: {1:s}'.format(image['Name'],
+                                               image['ImageId']))
diff --git a/examples/cli.py b/examples/cli.py
index 0dd3882..df24b2e 100644
--- a/examples/cli.py
+++ b/examples/cli.py
@@ -19,12 +19,13 @@
 import argparse
 import sys
 
-from typing import Tuple, List, Union, Optional
+from typing import Tuple, List, Optional
 from examples import aws_cli, gcp_cli
 
 PROVIDER_TO_FUNC = {
     'aws': {
         'copydisk': aws_cli.CreateVolumeCopy,
+        'listimages': aws_cli.ListImages,
         'listinstances': aws_cli.ListInstances,
         'listdisks': aws_cli.ListVolumes,
         'querylogs': aws_cli.QueryLogs,
@@ -134,6 +135,10 @@ def Main() -> None:
                 ('--attach_volumes', 'Comma seperated list of volume IDs '
                                      'to attach. Maximum of 11.', None)
             ])
+  AddParser('aws', aws_subparsers, 'listimages', 'List AMI images.',
+            args=[
+                ('--filter', 'Filter to apply to Name of AMI image.', None),
+            ])
 
   # GCP parser options
   gcp_parser.add_argument('project', help='Source GCP project.')
diff --git a/libcloudforensics/providers/aws/internal/account.py b/libcloudforensics/providers/aws/internal/account.py
index 9d1b598..244a2f8 100644
--- a/libcloudforensics/providers/aws/internal/account.py
+++ b/libcloudforensics/providers/aws/internal/account.py
@@ -744,3 +744,28 @@ class AWSAccount:
     # pylint: enable=line-too-long
     block_device_mapping['Ebs']['VolumeSize'] = boot_volume_size
     return block_device_mapping
+
+  # pylint: disable=line-too-long
+  def ListImages(self,
+                 qfilter: Optional[List[Dict[str, Any]]]) -> List[Dict[str, Any]]:
+    """List AMI images.
+
+    Args:
+      qfilter (List[Dict]): The filter expression.
+      See https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_images  # pylint: disable=line-too-long
+
+    Returns:
+      List[Dict[str, Any]]: The list of images with their properties.
+
+    Raises:
+      RuntimeError: If the images could not be listed.
+    """
+
+    client = self.ClientApi(common.EC2_SERVICE)
+    try:
+      images = client.describe_images(Filters=qfilter) # type: Dict[str, List[Dict[str, Any]]]
+    except client.exceptions.ClientError as exception:
+      raise RuntimeError(str(exception))
+
+    return images['Images']
+  # pylint: enable=line-too-long

Starting a forensic VM outside of AWS region us-east-2 errors out
We create an EC2 instance of the analysis VM based on the below AMI.
https://github.com/google/cloud-forensics-utils/blob/40c7c86701413b2d2efc48734bf35279e8a18149/libcloudforensics/providers/aws/internal/common.py#L29

This AMI seems not to exist anymore.
diff --git a/tests/providers/aws/aws_e2e.py b/tests/providers/aws/aws_e2e.py
index c30b63f..922d8cf 100644
--- a/tests/providers/aws/aws_e2e.py
+++ b/tests/providers/aws/aws_e2e.py
@@ -116,6 +116,20 @@ class EndToEndTest(unittest.TestCase):
     self.assertEqual(aws_volume.volume_id, volume_copy.volume_id)
     self._StoreVolumeForCleanup(aws_account, aws_volume)
 
+  @typing.no_type_check
+  def testListImages(self):
+    """End to end test on AWS.
+
+    Test listing AMI images with a filter.
+    """
+
+    aws_account = account.AWSAccount(self.zone)
+    qfilter = [{'Name':'name', 'Values':['Ubuntu 18.04*']}]
+    images = aws_account.ListImages(qfilter)
+
+    self.assertGreater(len(images), 0)
+    self.assertIn('Name', images[0])
+
   @typing.no_type_check
   def testEncryptedVolumeCopy(self):
     """End to end test on AWS.
diff --git a/tests/providers/aws/aws_test.py b/tests/providers/aws/aws_test.py
index ee9656f..b81291c 100644
--- a/tests/providers/aws/aws_test.py
+++ b/tests/providers/aws/aws_test.py
@@ -96,6 +96,13 @@ MOCK_DESCRIBE_INSTANCES_TAGS = {
     }]
 }
 
+MOCK_DESCRIBE_IMAGES = {
+    'Images' : [
+        {'Name': 'Ubuntu 18.04 LTS', 'Public': True},
+        {'Name': 'Ubuntu 18.04 with GUI', 'Public': False}
+    ]
+}
+
 MOCK_DESCRIBE_VOLUMES = {
     'Volumes': [{
         'VolumeId': FAKE_VOLUME.volume_id,
@@ -425,6 +432,15 @@ class AWSAccountTest(unittest.TestCase):
     # pylint: enable=protected-access
     self.assertEqual(50, config['Ebs']['VolumeSize'])
 
+  @typing.no_type_check
+  @mock.patch('libcloudforensics.providers.aws.internal.account.AWSAccount.ClientApi')
+  def testListImages(self, mock_ec2_api):
+    """Test that AMI images are correctly listed."""
+    describe_images = mock_ec2_api.return_value.describe_images
+    describe_images.return_value = MOCK_DESCRIBE_IMAGES
+    images = FAKE_AWS_ACCOUNT.ListImages(qfilter=None)
+    self.assertEqual(2, len(images))
+    self.assertIn('Name', images[0])
 
 class AWSInstanceTest(unittest.TestCase):
   """Test AWSInstance class."""

pip install -e .[dev]
parse_log_pytest
requirements.txt
mock pytest pytest-mock
apt-get update apt-get install -y gcc
3.7
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
boto3==1.33.13
botocore==1.33.13
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
exceptiongroup==1.2.2
google-api-core==2.24.2
google-api-python-client==2.166.0
google-auth==2.38.0
google-auth-httplib2==0.2.0
googleapis-common-protos==1.69.2
httplib2==0.22.0
idna==3.10
importlib-metadata==6.7.0
iniconfig==2.0.0
jmespath==1.0.1
-e git+https://github.com/google/cloud-forensics-utils.git@15dccb23ed1fc4fd93452962bde3010c7811247d#egg=libcloudforensics
mock==5.2.0
packaging==24.0
pluggy==1.2.0
proto-plus==1.26.1
protobuf==4.24.4
pyasn1==0.5.1
pyasn1-modules==0.3.0
pyparsing==3.1.4
pytest==7.4.4
pytest-mock==3.11.1
python-dateutil==2.9.0.post0
requests==2.31.0
rsa==4.9
s3transfer==0.8.2
six==1.17.0
tomli==2.0.1
typing_extensions==4.7.1
uritemplate==4.1.1
urllib3==1.26.20
zipp==3.15.0

name: cloud-forensics-utils
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=22.3.1=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - boto3==1.33.13
      - botocore==1.33.13
      - cachetools==5.5.2
      - charset-normalizer==3.4.1
      - exceptiongroup==1.2.2
      - google-api-core==2.24.2
      - google-api-python-client==2.166.0
      - google-auth==2.38.0
      - google-auth-httplib2==0.2.0
      - googleapis-common-protos==1.69.2
      - httplib2==0.22.0
      - idna==3.10
      - importlib-metadata==6.7.0
      - iniconfig==2.0.0
      - jmespath==1.0.1
      - mock==5.2.0
      - packaging==24.0
      - pluggy==1.2.0
      - proto-plus==1.26.1
      - protobuf==4.24.4
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pyparsing==3.1.4
      - pytest==7.4.4
      - pytest-mock==3.11.1
      - python-dateutil==2.9.0.post0
      - requests==2.31.0
      - rsa==4.9
      - s3transfer==0.8.2
      - six==1.17.0
      - tomli==2.0.1
      - typing-extensions==4.7.1
      - uritemplate==4.1.1
      - urllib3==1.26.20
      - zipp==3.15.0
prefix: /opt/conda/envs/cloud-forensics-utils

tests/providers/aws/aws_test.py::AWSAccountTest::testListImages
tests/providers/aws/aws_test.py::AWSAccountTest::testCreateVolumeFromSnapshot tests/providers/aws/aws_test.py::AWSAccountTest::testGenerateVolumeName tests/providers/aws/aws_test.py::AWSAccountTest::testGetBootVolumeConfigByAmi tests/providers/aws/aws_test.py::AWSAccountTest::testGetInstanceById tests/providers/aws/aws_test.py::AWSAccountTest::testGetInstancesByName tests/providers/aws/aws_test.py::AWSAccountTest::testGetInstancesByNameOrId tests/providers/aws/aws_test.py::AWSAccountTest::testGetOrCreateAnalysisVm tests/providers/aws/aws_test.py::AWSAccountTest::testGetVolumeById tests/providers/aws/aws_test.py::AWSAccountTest::testGetVolumesByName tests/providers/aws/aws_test.py::AWSAccountTest::testGetVolumesByNameOrId tests/providers/aws/aws_test.py::AWSAccountTest::testListInstances tests/providers/aws/aws_test.py::AWSAccountTest::testListVolumes tests/providers/aws/aws_test.py::AWSInstanceTest::testGetBootVolume tests/providers/aws/aws_test.py::AWSVolumeTest::testSnapshot tests/providers/aws/aws_test.py::AWSSnapshotTest::testCopy tests/providers/aws/aws_test.py::AWSCloudTrailTest::testLookupEvents tests/providers/aws/aws_test.py::AWSCommon::testCreateTags tests/providers/aws/aws_test.py::AWSCommon::testGetInstanceTypeByCPU tests/providers/aws/aws_test.py::AWSTest::testCreateVolumeCopy1 tests/providers/aws/aws_test.py::AWSTest::testCreateVolumeCopy2 tests/providers/aws/aws_test.py::AWSTest::testCreateVolumeCopy3
examples/cli.py examples/aws_cli.py libcloudforensics/providers/aws/internal/account.py
examples/cli.py examples/aws_cli.py libcloudforensics/providers/aws/internal/account.py