Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,19 @@ When generating code for this repository:
## Project Structure

```
wmi/ # Repository root (install to plugins/wmi/ in Cacti)
├── script/ # External wmic/collection helper scripts
├── templates/ # Data query/graph template XML
├── tests/ # Test suite
├── functions.php # Core hook callbacks and shared logic
├── linux_wmi.php # Linux (`wmic`) collection path
├── poller_wmi.php # Background poller entry point (CLI)
├── wmi_accounts.php # WMI credential administration
├── wmi_queries.php # WQL query administration
├── wmi_script.php # Ad hoc query/tool runner
├── wmi_tools.php # Diagnostic tools UI
├── INFO # Plugin metadata (name, version, compat)
wmi/ # Repository root (install to plugins/wmi/ in Cacti)
├── script/ # External wmic/collection helper scripts
├── templates/ # Data query/graph template XML
├── tests/ # Test suite
├── includes/ # functions.php (core hook callbacks/logic), database.php (schema), linux_wmi.php (wmic/CIM client)
├── poller_wmi.php # Background poller entry point (CLI)
├── wmi_accounts.php # WMI credential administration
├── wmi_queries.php # WQL query administration
├── wmi_script.php # Ad hoc query/tool runner
├── wmi_tools.php # Diagnostic tools UI
├── INFO # Plugin metadata (name, version, compat)
├── README.md
└── setup.php # Plugin install/uninstall/upgrade hooks
└── setup.php # Plugin install/uninstall/upgrade hooks
```

## Naming Conventions
Expand Down Expand Up @@ -194,3 +193,7 @@ existing code or adding new code, not just in dedicated cleanup passes:
line, `@param` lines, a blank comment line, then `@return`. Infer parameter/return types from
actual usage; don't change the function's real type-hints in the same pass (let static analysis
flag mismatches separately). Skip vendored third-party library files.

## File manifest & upgrade pruning

The plugin ships a root `manifest.json` with three arrays: `tombstones` (files/directories older versions shipped that have since moved or been removed), `expected` (the top-level files and directories that ship today, directories written with a trailing `/`), and `whitelist` (paths holding user data that must never be touched). Keep `expected` current: CI runs `tests/bin/validate-manifest.php`, which fails on any drift between `expected` and the real top-level tree (it ignores `tests/`, `phpunit.xml`, `.git*`, `.md*`, and whitelisted paths). Custom customer CSS/theme files belong in `expected`, and stylesheets live in `css/` (not `themes/`). On upgrade, `wmi_prune_files()` deletes the tombstoned paths, the dev-only `tests/` tree, and the `phpunit.xml` test config, leaves `whitelist`, `.git*`, and `.md*` alone, and logs (without removing) any top-level entry the manifest does not account for. As a safety measure it refuses any tombstone that resolves outside the plugin directory (a tampered manifest.json) and logs a warning for any file or directory it cannot remove. When you move or delete a shipped file, add its old path to `tombstones` and update `expected` in the same change.
3 changes: 3 additions & 0 deletions .github/workflows/plugin-ci-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ jobs:
- name: Check PHP version
run: php -v

- name: Validate plugin manifest (expected-file drift)
run: php cacti/plugins/wmi/tests/bin/validate-manifest.php

- name: Run apt-get update
run: sudo apt-get update

Expand Down
176 changes: 176 additions & 0 deletions includes/database.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php
/*
+-------------------------------------------------------------------------+
| Copyright (C) 2004-2026 The Cacti Group |
| |
| This program is free software; you can redistribute it and/or |
| modify it under the terms of the GNU General Public License |
| as published by the Free Software Foundation; either version 2 |
| of the License, or (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
+-------------------------------------------------------------------------+
| Cacti: The Complete RRDTool-based Graphing Solution |
+-------------------------------------------------------------------------+
| This code is designed, written, and maintained by the Cacti Group. See |
| about.php and/or the AUTHORS file for specific developer information. |
+-------------------------------------------------------------------------+
| http://www.cacti.net/ |
+-------------------------------------------------------------------------+
*/
Comment thread
TheWitness marked this conversation as resolved.

/**
* Adds the 'wmi_account' column to Cacti's host table, creates this
* plugin's database tables (wmi_user_accounts, wmi_wql_queries,
* host_wmi_query, host_template_wmi_query, host_wmi_accounts,
* host_wmi_cache, wmi_processes), and registers this plugin's two Data
* Input Methods ('Get WMI Data' and 'Get WMI Data (Indexed)') along with
* their input/output fields, if not already present. Called from
* plugin_wmi_install() during plugin installation.
*
* @return void
*/
function plugin_wmi_setup_tables() {
api_plugin_db_add_column('wmi', 'host',
[
'name' => 'wmi_account',
'type' => 'int(10)',
'unsigned' => true,
'NULL' => false,
'default' => '0',
'after' => 'disabled'
]
);

db_execute("CREATE TABLE IF NOT EXISTS `wmi_user_accounts` (
`id` int(11) UNSIGNED NOT NULL auto_increment,
`name` varchar(64) NOT NULL,
`username` varchar(64) NOT NULL,
`password` varchar(256) NOT NULL,
PRIMARY KEY (`id`))
ENGINE=InnoDB
COMMENT='Holds Account Information for WMI Queries'");

db_execute("CREATE TABLE IF NOT EXISTS `wmi_wql_queries` (
`id` int(11) UNSIGNED NOT NULL auto_increment,
`hash` varchar(32) NOT NULL default '',
`name` varchar(64) NOT NULL,
`frequency` mediumint(8) unsigned NOT NULL DEFAULT '86400',
`enabled` char(2) DEFAULT 'on',
`namespace` varchar(64) NOT NULL,
`query` varchar(1024) NOT NULL,
`primary_key` varchar(128) NOT NULL DEFAULT 'None',
PRIMARY KEY (`id`))
ENGINE=InnoDB
COMMENT='Holds WMI Queries for Devices'");

db_execute("CREATE TABLE IF NOT EXISTS `host_wmi_query` (
`host_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`wmi_query_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`sort_field` varchar(50) NOT NULL DEFAULT '',
`title_format` varchar(50) NOT NULL DEFAULT '',
`last_started` timestamp NOT NULL DEFAULT '0000-00-00',
`last_runtime` double NOT NULL DEFAULT '0.00',
`last_failed` timestamp NOT NULL DEFAULT '0000-00-00',
PRIMARY KEY (`host_id`,`wmi_query_id`))
ENGINE=InnoDB
COMMENT='Holds WMI Data Queries'");

db_execute("CREATE TABLE IF NOT EXISTS `host_template_wmi_query` (
`host_template_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`wmi_query_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`host_template_id`,`wmi_query_id`))
ENGINE=InnoDB
COMMENT='Holds Device Template WMI Queries'");

db_execute("CREATE TABLE IF NOT EXISTS `host_wmi_accounts` (
`id` int(11) UNSIGNED NOT NULL auto_increment,
`host_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`account_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `host_id` (`host_id`))
ENGINE=InnoDB
COMMENT='Holds Device WMI Accounts'");

db_execute("CREATE TABLE IF NOT EXISTS `host_wmi_cache` (
`host_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`wmi_query_id` mediumint(8) unsigned NOT NULL DEFAULT '0',
`field_name` varchar(50) NOT NULL DEFAULT '',
`field_value` varchar(4096) DEFAULT NULL,
`wmi_index` varchar(255) NOT NULL DEFAULT '',
`present` tinyint(4) NOT NULL DEFAULT '1',
`last_updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`host_id`,`wmi_query_id`,`field_name`,`wmi_index`),
KEY `host_id` (`host_id`,`field_name`),
KEY `wmi_index` (`wmi_index`),
KEY `field_name` (`field_name`),
KEY `field_value` (`field_value`),
KEY `wim_query_id` (`wmi_query_id`),
KEY `present` (`present`),
KEY `last_updated` (`last_updated`))
ENGINE=InnoDB
COMMENT='Holds Device WMI Information'");

db_execute("CREATE TABLE IF NOT EXISTS `wmi_processes` (
`pid` int(10) unsigned NOT NULL,
`taskid` int(10) unsigned NOT NULL,
`started` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY (`pid`))
ENGINE=MEMORY
COMMENT='Running wmi collector processes';");

$exists = db_fetch_cell('SELECT id FROM data_input WHERE hash="4af550dfe8b451579054d038ad62ba3e"');

if (!$exists) {
$save = [];
$save['hash'] = '4af550dfe8b451579054d038ad62ba3e';
$save['name'] = 'Get WMI Data';
$save['input_string'] = '';
$save['type_id'] = 7;
$id = sql_save($save, 'data_input');

if ($id) {
db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('e45cfa73589b88887725350a728d2ee9',$id,'The WMI Class Name','class','in','',0,'','','')");

db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('c5f782d783edec607f64bea9cccd533c',$id,'The WMI Column Name','column','in','',0,'','','')");
}
}

$exists = db_fetch_cell('SELECT id
FROM data_input
WHERE hash="42e584b81075f6ad6556e62afc509179"');

if (!$exists) {
$save = [];
$save['hash'] = '42e584b81075f6ad6556e62afc509179';
$save['name'] = 'Get WMI Data (Indexed)';
$save['input_string'] = '';
$save['type_id'] = 8;
$id = sql_save($save, 'data_input');

if ($id) {
db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('fb6317f2c49e494007e968283576d5a8',$id,'The WMI Class Name','class','in','',0,'','','')");

db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('cfebf9aa08f98bc1bfda7de2ebe12d94',$id,'The WMI Column Name','column','in','',0,'','','')");

db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('41798400f48141c25bc2407b5f5b1573',$id,'Output Type ID','output_type','in','',0,'output_type','','')");

db_execute("INSERT INTO `data_input_fields`
(hash, data_input_id, name, data_name, input_output, update_rra, sequence, type_code, regexp_match, allow_nulls)
VALUES ('02cd18a75a17e0a7d4ca28bc224630e0',$id,'Output Value','output','out','on',0,'','','')");
}
}
}
6 changes: 3 additions & 3 deletions functions.php → includes/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
+-------------------------------------------------------------------------+
*/

require_once(__DIR__ . '/linux_wmi.php');

/**
* Renders this plugin's tabbed navigation (Queries, Authentication) atop
* wmi_queries.php/wmi_accounts.php, highlighting the currently active tab
Expand Down Expand Up @@ -112,7 +114,7 @@ function plugin_wmi_query_exists($query) {
function plugin_wmi_create_dataquery_xml($id) {
global $config;

include_once($config['base_path'] . '/lib/export.php');
require_once($config['base_path'] . '/lib/export.php');
$wmic = db_fetch_row_prepared('SELECT * FROM wmi_wql_queries WHERE id = ?', [$id]);
$data = '';

Expand Down Expand Up @@ -445,8 +447,6 @@ function run_store_wmi_query($host_id, $wmi_query_id) {
// Cacti server and the wmic shell transport otherwise (see
// Linux_WMI::default_transport()), so the same client is used here
// regardless of $config['cacti_server_os'].
include_once($config['base_path'] . '/plugins/wmi/linux_wmi.php');

$wmi = new Linux_WMI();
$wmi->hostname = $host;
$wmi->username = $username;
Expand Down
File renamed without changes.
5 changes: 2 additions & 3 deletions locales/po/cacti.pot
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ msgid ""
msgstr ""
"Project-Id-Version: Cacti \n"
"Report-Msgid-Bugs-To: developers@cacti.net\n"
"POT-Creation-Date: 2026-09-28 16:09-0400\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Cacti Developers <developers@cacti.net>>\n"
"Language-Team: Cacti Developers <developers@cacti.net>\n"
Expand All @@ -17,11 +16,11 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#: functions.php wmi_queries.php wmi_tools.php
#: includes/functions.php wmi_queries.php wmi_tools.php
msgid "Queries"
msgstr ""

#: functions.php
#: includes/functions.php
msgid "Authentication"
msgstr ""

Expand Down
25 changes: 25 additions & 0 deletions manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"tombstones": [
"functions.php",
"linux_wmi.php"
],
"expected": [
"CHANGELOG.md",
"INFO",
"LICENSE",
"README.md",
"includes/",
"index.php",
"locales/",
"manifest.json",
"poller_wmi.php",
"script/",
"setup.php",
"templates/",
"wmi_accounts.php",
"wmi_queries.php",
"wmi_script.php",
"wmi_tools.php"
],
"whitelist": []
}
10 changes: 2 additions & 8 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,8 @@
<source>
<include>
<file>setup.php</file>
<file>functions.php</file>
<file>linux_wmi.php</file>
<file>poller_wmi.php</file>
<file>wmi_accounts.php</file>
<file>wmi_queries.php</file>
<file>wmi_script.php</file>
<file>wmi_tools.php</file>
<file>script/wmi-script.php</file>
<file>includes/functions.php</file>
<file>includes/linux_wmi.php</file>
</include>
</source>
</phpunit>
10 changes: 5 additions & 5 deletions poller_wmi.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
chdir(__DIR__);
chdir('../..');

include('./include/cli_check.php');
include_once('./lib/poller.php');
include_once('./lib/ping.php');
include_once('./plugins/wmi/functions.php');
require('./include/cli_check.php');
require_once('./lib/poller.php');
require_once('./lib/ping.php');
require_once('./plugins/wmi/includes/functions.php');

// process calling arguments
$parms = $_SERVER['argv'];
Expand Down Expand Up @@ -469,7 +469,7 @@ function display_version() {
global $config;

if (!function_exists('plugin_wmi_version')) {
include_once($config['base_path'] . '/plugins/wmi/setup.php');
require_once($config['base_path'] . '/plugins/wmi/setup.php');
}

$info = plugin_wmi_version();
Expand Down
4 changes: 2 additions & 2 deletions script/wmi-script.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
*/

if (!isset($called_by_script_server)) {
include_once(__DIR__ . '/../include/cli_check.php');
require_once(__DIR__ . '/../include/cli_check.php');
array_shift($_SERVER['argv']);

if (isset($_SERVER['argv'][0]) && $_SERVER['argv'][0] == 'wmi_script') {
Expand Down Expand Up @@ -66,7 +66,7 @@
function wmi_script($hostname, $host_id, $wmiquery, $cmd = '', $arg1 = '', $arg2 = '') {
global $config;

include_once($config['base_path'] . '/plugins/wmi/linux_wmi.php');
require_once($config['base_path'] . '/plugins/wmi/includes/linux_wmi.php');

$wmi = new Linux_WMI($host_id);
$wmi->hostname = $hostname;
Expand Down
Loading
Loading