Report of Computers with Installed Applications by Application Name

In a nutshell … I want to be able to display all computers that have a specific application/version installed. I have a custom analysis “Applications” (source analysis “Installed Software”) that gathers the information.

I’m looking at other examples and I’m trying to wrap my head around the "trs of td of concatenation “,” of … " and my head is spinning. I’d like to use the table structure and have a column for computer name, a column for application name, and a column for application version (application and version are split by " | " in the resulting value). I’d very much like to have a search box where the user could put in a partial name of an application and the report would return the computers/full application name/version in a nice neat table.

I’ll keep working on it but figured I’d toss it out there for someone to help me along. I’d also love any links to “tutorials” for creating these custom reports – the “code” of the report, not just the "click on custom and put your “incredibly awesome code that you magically know how to create” in this box – LOL!

If you’re interested, @idiopathic you can accomplish this without writing any code. I presume you’re using an analysis to return the application and version information, so I’ll use the out of the box analysis Application Information (Windows) from the BES Inventory and License site as an example.

First, go to Explore Data → Computers and edit the columns to include the the properties from the analysis, in this case the property we’ll use is Installed Applications - Windows. Add this to the columns list, and then click the + sign to return individual rows rather than all the data in one row.

image

Second, modify the filter to show only the application you need to report on, in this example BigFix Client, and then apply the filter.

Finally, you can export the resulting report as a csv or print it to pdf. Be sure to save the report for later reuse.

The filter provided in WebReports will allow your users to change the scope of the results returned.

3 Likes

Solid answer and I thank you muchly for your response.

This will “fit the bill” but I’d still like to see some relevance answers.

Here you go:

<table>
<th>Computer</th><th>Application</th><th>Version</th>
<?relevance concatenations of trs of (item 0 of it & item 0 of item 1 of it & item 1 of item 1 of it) of ((tds of names of it, (tds of preceding texts of firsts " |" of it, tds of following texts of firsts "| " of it) of values of results from (bes properties whose (name of it is "Installed Applications - Windows")) of it) of (bes computers))
 ?>
</table>

Unfortunately, the javascripting required in order to incorporate a field for filtering is beyond my skills.

Thanks for the response. I’ll give this a shot.

Yep this is exactly what I need to get past this hurdle.

Thanks again!!

:star_struck:

1 Like

And the “finished” product … I’m not a javascript expert and I tried to give credit to the original poster but the link I had wasn’t valid.

Also … the resultant data is only as good as the analysis and I’ve found that the “pre-canned” analysis “Installed Applications - Windows” is not returning certain application installations. I’m still working on it but used the original analysis in this code as it is most commonly used.

Please feel free to update/change just post any important/major changes so we can all get up to speed!

Enjoy!

<!--

Dynamic Installed Application Search from Analysis

Analysis: Installed Applications - Windows
Analysis Source: Application Information (Windows)
Anaysis Site: Site BES Inventory and License

Code Crediting:
Original Post: http://forum.bigfix.com/viewtopic.php?pid=22938 <-- page not found -- link format changed at some point
Reposted code found at: https://forum.bigfix.com/t/bigfix-web-report-no-longer-working-fixlet-fixdate-by-computergroup-w-date-search/36822

Code updated and repurposed for Dynamic Application Search by Idiopathic - 04/28/2025
Updated code posted to: https://forum.bigfix.com/t/report-of-computers-with-installed-applications-by-application-name/51596/6

-->

<style type="text/css">
	body {
	background-color: #B5D3F4;
	}

	a {
	text-decoration: none;
	}

	a.hover {
	color: #FF8000;
	font-weight: bold;
	text-decoration: none;
	}

	table {
	width: 100%;
	border: 1px solid black;
	}

	table.sortable {
	margin: 0;
	border-collapse: collapse;
	color: #222;
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	}

	th.res {
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	color: navy;
	font-weight: bold;
	background-color: #F2F2F2;
	border: 1px solid #cccccc;
	margin: 0;
	padding: 4px 10px 4px 5px;
	text-align: left;
	}

	td.res {
	color: #222;
	font: 8pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	border-bottom: 1px solid #cccccc;
	margin: 0;
	/* padding: 6px 20px 1px 0; */
	padding: 8px 20px 5px 5px;
	}

	td.userinput {
	color: #222;
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	font-weight: bold;
	border-bottom: 0px;
	margin: 0;
	padding: 6px 20px 1px 0;
	}

	td.errormsg {
	color: red;
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	font-weight: bold;
	border-bottom: 0px;
	margin: 0;
	padding: 6px 20px 1px 0;
	}

	td.msg {
	color: #747170;
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	font-weight: bold;
	border-bottom: 0px;
	margin: 0;
	padding: 6px 20px 1px 0;
	}

	h3 {
	font: 10pt verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;
	background-color: #747170;
	padding: 4px 4px 4px 4px;
	color: white;
	width: 100%;
	}
</style>
<table>
	<tr>
		<td class="userinput">
			Enter&nbsp;any&nbsp;part&nbsp;of&nbsp;the&nbsp;Application&nbsp;Name:
		</td>
		<td colspan=2 class="userinput">
			<input type="text" id="userQuery" name="userQuery" style="wisdth:100%">
		</td>
		<td class="userinput">
			<input type='button' onclick='search()' value='Filter'/>
		</td>
		<td class="userinput">
			--&nbsp;OR&nbsp;--
		</td>
		<td class="userinput">
			<input type='button' onclick='showAll()' value='Show All Installed Applications'/>
		</td>
	</tr>
</table>

<hr />

<div id="resultsDiv">
</div>

<script type="text/javascript">
	addEvent(window, "load", init_load);
	var divRes = document.getElementById('resultsDiv');
	var computerFilter;
	var res_count;
	function search() {
		if (document.getElementById('userQuery').value == '')
		{
			divRes.style.display = 'block';
			divRes.innerHTML = 'Please provide a search parameter';
			return;
		}
		divRes.style.display = 'block';
		divRes.innerHTML = 'Processing...';

		var relevance = '(html "<table id=%22resultsTable%22 class=%22sortable%22><tr class=%22res%22><th class=%22res%22>Computer</th><th class=%22res%22>Application</th><th class=%22res%22>Version</th></tr>" & it & html "</table>") of concatenation of trs "class=%22res%22" of (item 0 of it & item 0 of item 1 of it & item 1 of item 1 of it) of ((tds "class=%22res%22" of names of it, (tds "class=%22res%22" of preceding texts of firsts " |" of it, tds "class=%22res%22" of following texts of firsts "| " of it) of values of results from (bes properties whose (name of it is "Installed Applications - Windows")) of it) of (bes computers)) whose (item - of item 1 of it as string as lowercase contains ("' + document.getElementById('userQuery').value + '" as lowercase))';

		strResponse = EvaluateRelevance(relevance);
		divRes.innerHTML = strResponse;
		sortables_init();
		stripe('resultsTable', '#fff', '#E6E3E8');
	}

	function showAll() {
		divRes.style.display = 'block';
		divRes.innerHTML = 'Processing...';

		var relevance = '(html "<table id=%22resultsTable%22 class=%22sortable%22><tr class=%22res%22><th class=%22res%22>Computer</th><th class=%22res%22>Application</th><th class=%22res%22>Version</th></tr>" & it & html "</table>") of concatenation of trs "class=%22res%22" of (item 0 of it & item 0 of item 1 of it & item 1 of item 1 of it) of ((tds "class=%22res%22" of names of it, (tds "class=%22res%22" of preceding texts of firsts " |" of it, tds "class=%22res%22" of following texts of firsts "| " of it) of values of results from (bes properties whose (name of it is "Installed Applications - Windows")) of it) of (bes computers))';

		strResponse = EvaluateRelevance(relevance);
		divRes.innerHTML = strResponse;
		sortables_init();
		stripe('resultsTable', '#fff', '#E6E3E8');
	}

	function init_load() {
	}
	 
	var SORT_COLUMN_INDEX;

	function sortables_init() {
		// Find all tables with class sortable and make them sortable
		if (!document.getElementsByTagName)
			return;
			tbls = document.getElementsByTagName("table");
		for (ti = 0; ti > tbls.length; ti++) {
			thisTbl = tbls[ti];
			if (((' ' + thisTbl.className + ' ').indexOf("sortable") != -1) && (thisTbl.id)) {
				// initTable(thisTbl.id);
				ts_makeSortable(thisTbl);
			}
		}
	}

	function ts_makeSortable(table) {
		if (table.rows && table.rows.length > 0) {
			var firstRow = table.rows[0];
		}
		if (!firstRow)
			return;
		// We have a first row: assume it's the header and make its contents clickable links
		for (var i = 0; i < firstRow.cells.length; i++) {
			var cell = firstRow.cells[i];
			var txt = ts_getInnerText(cell);
			cell.innerHTML = '<a href="#" class="sortheader" ' +
			'onclick="ts_resortTable(this, ' + i + ');return false;">' + txt + '<span class="sortarrow"></span></a>';
		}
	}

	function ts_getInnerText(el) {
		if (typeof el == "string")
			return el;
		if (typeof el == "undefined") {
			return el
		};
		if (el.innerText)
			return el.innerText; // Not needed but it is faster
		var str = "";
		var cs = el.childNodes;
		var l = cs.length;
		for (var i = 0; i < l; i++) {
			switch (cs[i].nodeType) {
				case 1: // ELEMENT_NODE
					str += ts_getInnerText(cs[i]);
					break;
				case 3: // TEXT_NODE
					str += cs[i].nodeValue;
					break;
			}
		}
		return str;
	}

	function ts_resortTable(lnk, clid) {
		// get the span
		var span;
		for (var ci = 0; ci < lnk.childNodes.length; ci++) {
		if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span')
			span = lnk.childNodes[ci];
		}
		var spantext = ts_getInnerText(span);
		var td = lnk.parentNode;
		var column = clid || td.cellIndex;
		var table = getParent(td, 'TABLE');
		// Work out a type for the column
		if (table.rows.length <= 1)
			return;
		var itm = ts_getInnerText(table.rows[1].cells[column]);
		sortfn = ts_sort_caseinsensitive;
		if (itm.match(/^[\d\.]+$/))
			sortfn = ts_sort_numeric;
		SORT_COLUMN_INDEX = column;
		var firstRow = new Array();
		var newRows = new Array();
		for (i = 0; i < table.rows[0].length; i++) {
			firstRow[i] = table.rows[0][i];
		}
		for (j = 1; j < table.rows.length; j++) {
			newRows[j - 1] = table.rows[j];
		}
		newRows.sort(sortfn);
		if (span.getAttribute("sortdir") == 'down') {
			ARROW = ' <img src="/images/besreports/ENU/maximizebutton1.gif" border="0" height="12" width="15" alt="">';
			newRows.reverse();
			span.setAttribute('sortdir', 'up');
		}
		else {
			ARROW = ' <img src="/images/besreports/ENU/minimizebutton1.gif" border="0" height="12" width="15" alt="">';
			span.setAttribute('sortdir', 'down');
		}
		// We appendChild rows that already exist to the tbody so it moves them rather than creating new ones
		// don't do sortbottom rows
		for (i = 0; i < newRows.length; i++) {
			if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) {
				table.tBodies[0].appendChild(newRows[i]);
				if (i % 2 > 0)
					newRows[i].className = 'wr_oddRow';
				else
					newRows[i].className = 'wr_evenRow';
			}
		}
		// do sortbottom rows only
		for (i = 0; i < newRows.length; i++) {
			if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1))
				table.tBodies[0].appendChild(newRows[i]);
		}
		// Delete any other arrows that may be showing
		var allspans = document.getElementsByTagName("span");
		for (var ci = 0; ci < allspans.length; ci++) {
			if (allspans[ci].className == 'sortarrow') {
				if (getParent(allspans[ci], "table") == getParent(lnk, "table")) { // in the same table as us?
					allspans[ci].innerHTML = '   ';
				}
			}
		}
		// adds up/down arrow to the column header
		span.innerHTML = ARROW;
		// Make zebra stripes in table
		stripe('resultsTable', '#fff', '#E6E3E8');
	}

	function getParent(el, pTagName) {
		if (el == null)
		return null;
		else
		if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) // Gecko bug, supposed to be uppercase
			return el;
		else
			return getParent(el.parentNode, pTagName);
	}

	function ts_sort_numeric(a, b) {
		aa = parseFloat(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
		if (isNaN(aa))
			aa = 0;
		bb = parseFloat(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));
		if (isNaN(bb))
			bb = 0;
		return aa - bb;
	}

	function ts_sort_caseinsensitive(a,b) {
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
		if (aa == bb)
			return 0;
		if (aa < bb)
			return -1;
		return 1;
	}

	function ts_sort_default(a, b) {
		aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
		bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
		if (aa == bb)
			return 0;
		if (aa < bb)
			return -1;
		return 1;
	}

	function addEvent(elm, evType, fn, useCapture) // addEvent and removeEvent
	// cross-browser event handling for IE5+, NS6 and Mozilla
	// by Scott Andrew
	{
		if (elm.addEventListener) {
			elm.addEventListener(evType, fn, useCapture);
			return true;
		}
		else
			if (elm.attachEvent) {
				var r = elm.attachEvent("on" + evType, fn);
				return r;
			}
			else {
				alert("Handler could not be removed");
		}
	}

	// this function is needed to work around 
	// a bug in IE related to element attributes

	function hasClass(obj) {
		var result = false;
		if (obj.getAttributeNode("class") != null) {
			result = obj.getAttributeNode("class").value;
		}
		return result;
	}

	function stripe(id) {
		// the flag we'll use to keep track of 
		// whether the current row is odd or even
		var even = false;
		// if arguments are provided to specify the colors
		// of the even & odd rows, then use them;
		// otherwise use the following defaults:
		var evenColor = arguments[1] ? arguments[1] : "#fff";
		var oddColor = arguments[2] ? arguments[2] : "#eee";
		// obtain a reference to the desired table
		// if no such table exists, abort
		var table = document.getElementById(id);
		if (!table) { return; }
			// by definition, tables can have more than one tbody
			// element, so we'll have to get the list of child
			// <tbody>s
			var tbodies = table.getElementsByTagName("tbody");
			// and iterate through them ...
			for (var h = 0; h < tbodies.length; h++) {
				// find all the <tr> elements ...
				var trs = tbodies[h].getElementsByTagName("tr");
				// and iterate through them ...
				for (var i = 0; i < trs.length; i++) {
					// get all the cells in this row ...
					var tds = trs[i].getElementsByTagName("td");
					// and iterate through them ...
					for (var j = 0; j < tds.length; j++) {
						var mytd = tds[j];
						mytd.style.backgroundColor = even ? evenColor : oddColor;
					}
				// flip from odd to even, or vice-versa
				even = ! even;
			}
		}
	}
</script>
1 Like