diff --git a/agent_based/inv_juniper_hw_modules.py b/agent_based/inv_juniper_hw_modules.py
new file mode 100644
index 0000000000000000000000000000000000000000..8b6ad2e41ebfc3507be0edcb4541abe8d4d6b7d6
--- /dev/null
+++ b/agent_based/inv_juniper_hw_modules.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# License: GNU General Public License v2
+#
+# Author: thl-cmk[at]outlook[dot]com
+# URL   : https://thl-cmk.hopto.org
+# Date  : 2022-06-02
+#
+# inventory of juniper hardware modules
+#
+
+
+import time
+from typing import List, NamedTuple, Optional, Dict
+
+from cmk.base.plugins.agent_based.agent_based_api.v1.type_defs import (
+    StringTable,
+    InventoryResult,
+)
+
+from cmk.base.plugins.agent_based.agent_based_api.v1 import (
+    register,
+    SNMPTree,
+    TableRow,
+    startswith,
+)
+
+
+def parse_inv_juniper_hw_modules(string_table: List[StringTable]) -> List[Dict]:
+    modules = []
+
+    jnx_contents_entry, jnx_box_anatomy, sys_uptime = string_table
+    sys_uptime = int(sys_uptime[0][0]) / 100  # change to seconds
+    # first entry in jnx_contents_entry is the chassis without data (?)
+    if jnx_contents_entry[0][5:9] == ['', '', '', '0']:
+        box_class, box_descr, box_serial_no, box_revision, box_installed = jnx_box_anatomy[0]
+        jnx_contents_entry[0][5] = box_descr
+        jnx_contents_entry[0][6] = box_serial_no
+        jnx_contents_entry[0][7] = box_revision
+        jnx_contents_entry[0][8] = box_installed
+
+    for container_index, l1_index, l2_index, l3_index, type_oid, descr, serial_no, revision, installed, part_no, \
+        chassis_id, chassis_descr, chassis_clei_code, model in jnx_contents_entry:
+
+        index = f'{container_index}.{l1_index}.{l2_index}.{l3_index}'
+        installed = time.ctime(time.time() - sys_uptime + (int(installed))) if installed != '0' else None
+
+        module = {}
+        for key, value in [
+            ('index', index),
+            ('description', descr),
+            ('type', type_oid),
+            ('serial_number', serial_no),
+            ('revision', revision),
+            # ('installed_at', installed),
+            ('part_no', part_no),
+            ('chassis_id', chassis_id),
+            ('chassis_descr', chassis_descr),
+            ('chassis_clei_code', chassis_clei_code),
+            ('model', model),
+        ]:
+            if value:
+                module.update({key: value})
+
+        modules.append(module)
+
+    return modules
+
+
+def inventory_hw_modules(params, section: List[Dict]) -> InventoryResult:
+    removecolumns = params.get('removecolumns', [])
+    allparts = params.get('allparts', False)
+
+    path = ['hardware', 'juniper-hw-modules']
+
+    for module in section:
+        # do not add parts without serial number
+        if allparts is False and module.get('serial_number', None) is None:
+            continue
+
+        key_columns = {'index': module['index']}
+        for key in key_columns.keys():
+            module.pop(key)
+
+        for entry in removecolumns:
+            try:
+                module.pop(entry)
+            except KeyError:
+                pass
+
+        yield TableRow(
+            path=path,
+            key_columns=key_columns,
+            inventory_columns=module
+        )
+
+
+register.snmp_section(
+    name='inv_juniper_hw_modules',
+    parse_function=parse_inv_juniper_hw_modules,
+    fetch=[
+        SNMPTree(
+            base='.1.3.6.1.4.1.2636.3.1.8.1',  # JUNIPER-MIB::jnxContentsEntry
+            oids=[
+                '1',  # jnxContentsContainerIndex
+                '2',  # jnxContentsL1Index
+                '3',  # jnxContentsL2Index
+                '4',  # jnxContentsL3Index
+                '5',  # jnxContentsType
+                '6',  # jnxContentsDescr
+                '7',  # jnxContentsSerialNo
+                '8',  # jnxContentsRevision
+                '9',  # jnxContentsInstalled
+                '10',  # jnxContentsPartNo
+                '11',  # jnxContentsChassisId
+                '12',  # jnxContentsChassisDescr
+                '13',  # jnxContentsChassisCleiCode
+                '14',  # jnxContentsModel
+            ]
+        ),
+        SNMPTree(
+            base='.1.3.6.1.4.1.2636.3.1',  # UNIPER-MIB::jnxBoxAnatomy
+            oids=[
+                '1',  # jnxBoxClass
+                '2',  # jnxBoxDescr
+                '3',  # jnxBoxSerialNo
+                '4',  # jnxBoxRevision
+                '5',  # jnxBoxInstalled
+            ]
+        ),
+        SNMPTree(
+            base='.1.3.6.1.2.1.1',  # SNMPv2-MIB::system
+            oids=[
+                '3',  # sysUpTime
+            ]
+        )
+    ],
+    detect=startswith('.1.3.6.1.2.1.1.2.0', '.1.3.6.1.4.1.2636.1.1.1'),
+)
+
+register.inventory_plugin(
+    name='inv_juniper_hw_modules',
+    inventory_function=inventory_hw_modules,
+    inventory_default_parameters={
+        'removecolumns': [],
+        'allparts': False,
+    },
+    inventory_ruleset_name='inv_juniper_hw_modules',
+)
diff --git a/inv_juniper_hw_modules.mkp b/inv_juniper_hw_modules.mkp
new file mode 100644
index 0000000000000000000000000000000000000000..395b174af71436112eb6d6ff4ba5a7a6eb64816d
Binary files /dev/null and b/inv_juniper_hw_modules.mkp differ
diff --git a/packages/inv_juniper_hw_modules b/packages/inv_juniper_hw_modules
new file mode 100644
index 0000000000000000000000000000000000000000..7f722a6b2b03bf6befb447b2232748ad5cfcd7e7
--- /dev/null
+++ b/packages/inv_juniper_hw_modules
@@ -0,0 +1,13 @@
+{'author': 'Th.L. (thl-cmk[at]outlook[dot]com)',
+ 'description': 'Inventory of Juniper networks hardware modules\n',
+ 'download_url': 'https://thl-cmk.hopto.org',
+ 'files': {'agent_based': ['inv_juniper_hw_modules.py'],
+           'web': ['plugins/views/inv_juniper_hw_modules.py',
+                   'plugins/wato/inv_juniper_hw_modules.py']},
+ 'name': 'inv_juniper_hw_modules',
+ 'num_files': 3,
+ 'title': 'Juniper networks hardware inventory',
+ 'version': '20220602.v.0.01',
+ 'version.min_required': '2.0.0',
+ 'version.packaged': '2021.09.20',
+ 'version.usable_until': None}
\ No newline at end of file
diff --git a/web/plugins/views/inv_juniper_hw_modules.py b/web/plugins/views/inv_juniper_hw_modules.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a7daa9192432f2af06ab3252e85a38a74860247
--- /dev/null
+++ b/web/plugins/views/inv_juniper_hw_modules.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# import cmk.gui.utils
+from cmk.gui.plugins.views import (
+    inventory_displayhints, )
+from cmk.gui.i18n import _
+from cmk.gui.plugins.views.inventory import declare_invtable_view
+
+inventory_displayhints.update({
+    '.hardware.juniper-hw-modules:': {'title': _('Modules'),
+                                      'keyorder': ['index', 'description', 'serial_number', 'revision', 'part_no', ],
+                                      'view': 'invmodules_of_host',
+                                      },
+    '.hardware.juniper-hw-modules:*.index': {'title': _('Index'), },
+    '.hardware.juniper-hw-modules:*.serial_number': {'title': _('Serial number')},
+    '.hardware.juniper-hw-modules:*.description': {'title': _('Description')},
+    '.hardware.juniper-hw-modules:*.revision': {'title': _('Revision'), },
+    '.hardware.juniper-hw-modules:*.installed_at': {'title': _('Installed at'), },
+    '.hardware.juniper-hw-modules:*.part_no': {'title': _('Part No'), },
+    '.hardware.juniper-hw-modules:*.chassis_id': {'title': _('Chassis ID'), },
+    '.hardware.juniper-hw-modules:*.chassis_descr': {'title': _('Chassis description')},
+    '.hardware.juniper-hw-modules:*.chassis_clei_code': {'title': _('Chassis CLEI code'), },
+    '.hardware.juniper-hw-modules:*.model': {'title': _('Model'), },
+    '.hardware.juniper-hw-modules:*.type': {'title': _('Type (OID)'), },
+})
+
+declare_invtable_view(
+    'invmodules',
+    '.hardware.juniper-hw-modules:',
+    _('Juniper Hardware Modules'),
+    _('Juniper Hardware Modules')
+)
diff --git a/web/plugins/wato/inv_juniper_hw_modules.py b/web/plugins/wato/inv_juniper_hw_modules.py
new file mode 100644
index 0000000000000000000000000000000000000000..1dd1865eeebf90c5e4ca0d990e36ef6a28c1eb78
--- /dev/null
+++ b/web/plugins/wato/inv_juniper_hw_modules.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+#
+
+
+from cmk.gui.i18n import _
+from cmk.gui.plugins.wato import (
+    HostRulespec,
+    rulespec_registry,
+    RulespecGroup,
+)
+from cmk.gui.valuespec import (
+    Dictionary,
+    FixedValue,
+    TextAscii,
+    ListChoice,
+)
+
+from cmk.gui.plugins.wato.inventory import (
+    RulespecGroupInventory,
+)
+
+_removecolumns = [
+    ('chassis_clei_code', 'Chassis CLEI code'),
+    ('chassis_descr', 'Chassis description'),
+    ('chassis_id', 'Chassis ID'),
+    # ('installed_at', 'Installed at'),
+    ('model', 'Model'),
+    ('type', 'Type (OID)'),
+]
+
+
+def _valuespec_inv_juniper_hw_modules():
+    return Dictionary(
+        title=_('Juniper hardware inventory'),
+        elements=[
+            ('allparts',
+             FixedValue(
+                 True,
+                 title=_('Inventory parts without serial number'),
+                 totext=_(''),
+                 default_value=False,
+             )),
+            ('removecolumns',
+             ListChoice(
+                 title=_('List of columns to remove'),
+                 help=_('information to remove from inventory'),
+                 choices=_removecolumns,
+                 default_value=[
+                 ],
+             )),
+        ],
+    )
+
+
+rulespec_registry.register(
+    HostRulespec(
+        group=RulespecGroupInventory,
+        match_type="dict",
+        name="inv_parameters:inv_juniper_hw_modules",
+        valuespec=_valuespec_inv_juniper_hw_modules,
+    ))