# Getting Started with Custom Inventory

> Collect your own data on Windows endpoints with a script, and find it on each endpoint's Custom inventory tab.

Source: https://docs.capaone.com/capaone/windows-management/getting-started-with-custom-inventory/  
Product: CapaOne — a separate CapaSystems product; do not apply this page to any other.

Custom inventory is your own data about your Windows endpoints, for example the office an endpoint
belongs to. A script writes the data on the endpoint, and the CapaOne Agent sends it with its
hardware inventory. CapaOne shows it on the endpoint's page, under **Inventory → Custom**.

In this walkthrough, you create a script that writes custom inventory, run it on a test endpoint,
and remove the data again.

:::note[Before you start]
- Scripts require Application Manager.
- You need a Windows endpoint to test on, with CapaOne Agent 1.2.273 or later. Agents update
  automatically.
:::

## 1. Create a script

1. Go to **Windows → Applications** and select the **Scripts** tab.
2. Click **New**.
3. Enter a **Name**, for example `Custom Inventory Demo`, and a **Version**, for example `1.0`.
4. Under **PowerBricks**, click **Open PowerBrick Editor**.
5. Search for **Execute PowerShell Snippet**, and add it to the **Install** tab of the
   **Execution Sequence**.
6. Add a second **Execute PowerShell Snippet** to the **Uninstall** tab. It removes the demo data
   when you uninstall the script.

Leave the editor open for the next step. [Create a Script](/capaone/application-manager/create-a-script/)
describes the script's other settings, and
[Execute PowerShell Snippet](/capaone/application-manager/powerbricks/bricks/execute-powershell-snippet/)
describes the brick.

## 2. Add the demo code

The code uses the custom inventory functions of the PowerShell Scripting Library, which you call in
the snippet as `$cs.CustomInventory_*`. It writes two categories:

- **Office** is a category without instances. The endpoint has one set of values, written with the
  instance `''`. Replace the values with your own, for example in one script per office.
- **Printers** is a category with instances: one instance per printer, with its driver, its port,
  and whether it's shared. The code also removes printers that are gone since the script last ran.

1. Open the brick on the **Install** tab, and paste this code into **PowerShell Snippet**:

   ```powershell
   # A category without instances: one set of values for the device.
   # Replace the values with your own, for example in one script per office.
   $cs.CustomInventory_SetValue('Office', '', 'City', 'Copenhagen')
   $cs.CustomInventory_SetValue('Office', '', 'Floor', 3)
   $cs.CustomInventory_SetValue('Office', '', 'Has Parking', $true)

   # A category with instances: one instance per printer.
   $printers = @{}
   foreach ($printer in Get-Printer -ErrorAction SilentlyContinue) {
       # Names can only contain letters, digits, spaces and -, so replace other characters with a space.
       $instance = ($printer.Name -replace '[^\p{L}\p{Nd} -]', ' ' -replace ' {2,}', ' ').Trim()
       $printers[$instance] = $true
       $cs.CustomInventory_SetValue('Printers', $instance, 'Driver', $printer.DriverName)
       $cs.CustomInventory_SetValue('Printers', $instance, 'Port', $printer.PortName)
       $cs.CustomInventory_SetValue('Printers', $instance, 'Shared', $printer.Shared)
   }

   # Remove the printers that are gone since the script last ran.
   foreach ($instance in $cs.CustomInventory_GetInstanceNames('Printers')) {
       if (-not $printers.ContainsKey($instance)) {
           $cs.CustomInventory_RemoveInstance('Printers', $instance)
       }
   }
   ```

2. Click **Apply changes**.
3. Open the brick on the **Uninstall** tab, and paste this code into **PowerShell Snippet**:

   ```powershell
   $cs.CustomInventory_RemoveCategory('Office')
   $cs.CustomInventory_RemoveCategory('Printers')
   ```

4. Click **Apply changes**, and then **Close**.
5. Click **Create**. The script is created with the release status **Testing**.

Category, instance, and value names can only contain letters, digits, spaces, and `-`. That's why
the code replaces the other characters in printer names: `OneNote (Desktop)` becomes
`OneNote Desktop`. A name with other characters makes the script fail, and the installation log
says which characters aren't allowed. Open the log with **View logs** in the endpoint's row menu on
the script's **Endpoints** tab.

## 3. Run the script on a test endpoint

1. Open the script, select the **Assignment** tab, and assign the script to your test endpoint
   under **Direct**. CapaOne asks you to confirm, because the script is in **Testing**. See
   [Assign and Redeploy Windows Applications](/capaone/application-manager/assign-and-redeploy-windows-applications/).
2. Select the script's **Endpoints** tab, and wait until the script has run on the endpoint.
3. Open the endpoint's page, open the actions menu (⋮) in the header, point to **Request**, and
   select **Scan hardware**. The agent sends custom inventory with its hardware inventory, so the
   request delivers it now instead of with the next scan.
4. Select **Inventory → Custom**.

The **Custom** tab lists your categories. **Office** has one card, named after the category, and
**Printers** has one card per printer. Each card ends with **Last Updated**, the time a value on
the card last changed.

## 4. Keep the data current

The values stay on the endpoint until a script removes them, and the agent sends them with every
hardware inventory. The script itself runs once on each endpoint that it's assigned to.

To run the script again, for example every week so the list of printers stays current, use a
workflow with the **Install script** action and **Redeploy if assigned**. See
[Repeat a Company App or script on a schedule](/capaone/application-manager/automation/#repeat-a-company-app-or-script-on-a-schedule).

## 5. Remove the demo data

1. On the script's **Endpoints** tab, open the endpoint's row menu and select **Uninstall**. The
   **Uninstall** sequence removes both categories.
2. When the script has been uninstalled, send **Scan hardware** again.

The endpoint's **Custom** tab shows **No Custom Inventory** again, unless other scripts write
custom inventory on the endpoint.

## Next steps

### Find endpoints by their custom inventory

Use the MCP server or the REST API to search, count, and group endpoints by their custom inventory.
For example, find the endpoints in the Copenhagen office. A new field can be filtered on within an
hour after an endpoint first reports it. See
[Custom inventory fields](/capaone/mcp-and-api/filtering-devices/#custom-inventory-fields).

### Custom inventory functions

The PowerShell Scripting Library has these functions for custom inventory:

| Function | What it does |
|---|---|
| [$cs.CustomInventory_SetValue](/capainstaller/powerpacks/cs-custominventory-setvalue/) | Writes a value, optionally with a unit such as `bytes` or `date`. Creates the category and instance if they don't exist. |
| [$cs.CustomInventory_GetValue](/capainstaller/powerpacks/cs-custominventory-getvalue/) | Reads a value without its unit. |
| [$cs.CustomInventory_GetInstance](/capainstaller/powerpacks/cs-custominventory-getinstance/) | Returns all values of an instance. |
| [$cs.CustomInventory_GetCategory](/capainstaller/powerpacks/cs-custominventory-getcategory/) | Returns all instances of a category with their values. |
| [$cs.CustomInventory_GetCategoryNames](/capainstaller/powerpacks/cs-custominventory-getcategorynames/) | Returns the names of the categories on the endpoint. |
| [$cs.CustomInventory_GetInstanceNames](/capainstaller/powerpacks/cs-custominventory-getinstancenames/) | Returns the names of the instances in a category. |
| [$cs.CustomInventory_RemoveValue](/capainstaller/powerpacks/cs-custominventory-removevalue/) | Removes a value. |
| [$cs.CustomInventory_RemoveInstance](/capainstaller/powerpacks/cs-custominventory-removeinstance/) | Removes an instance with all its values. |
| [$cs.CustomInventory_RemoveCategory](/capainstaller/powerpacks/cs-custominventory-removecategory/) | Removes a category with all its instances and values. |

[$cs.CustomInventory_SetValue](/capainstaller/powerpacks/cs-custominventory-setvalue/) also
describes the names, the units, and the value types in detail.
