Creating and installing a custom Munki Report PHP module

As usual the code for my module is available on my Github so you can grab those files for a quick start if you like.

So I’ve been using MunkiReport-PHP a bit lately and found it is pretty useful.

However I found there was some information that I would like to gather from the client and display it in MunkiReport so I went about creating a custom reporting module.

It was pretty straight forward, but the documentation on the MunkiReport site is a bit vague, so hopefully this helps fill in the gaps.

First of all we need to work out what information we want to gather and display in Munki Report. In my situation I decided I wanted to show some status information from the SCCM Agent such as wether or not the client is enrolled, when the last check in time was, the management point and the client certificate expiration date. Basically the important stuff from the pref pane

pref

To gather this information I started with a script that looked a bit like this:

#!/bin/bash

# Location of the SCCM Plist
ccmpref="/var/root/Library/Preferences/com.microsoft.ccmclient.plist"
# Lets get the enrollment status
status=`defaults read $ccmpref EnrollmentStatus`

getmoreinfo() # If we are enrolled, lets pull some more info
{
echo "Status = Enrolled"

# Get the Management point
mp=`defaults read $ccmpref MP`
echo "Management_Point = $mp"

# Get the user name of the user who enrolled device
enrolluser=`defaults read $ccmpref EnrollmentUserName`
echo "Enrollment_User_Name = $enrolluser"

# Get the Enrollment Server Address
enrollserver=`defaults read $ccmpref EnrollmentServerName`
echo "Enrollment_Server_Address = $enrollserver"

# Get the last Check in time
checkin=`defaults read $ccmpref OMALastConnectTime`
echo "Last_Check_In_Time = $checkin"

# Get the Certificate Expiration date
certexp=`defaults read $ccmpref CertExpDate`
echo "Client_Certificate_Expiry_Date = $certexp"
}

# Determine if we are enrolled
if [ $status = "0" ]; 
	then
	echo "Status = Not Enrolled"
elif [ $status = "1" ];
	then
	getmoreinfo
fi 

So now atleast we can gather the information we need. On to the next step of getting this into a MunkiReport module

I found the easiest way to create the module was to simply copy an existing one.

So on our MunkiReport server, go into /var/www/munkireport/app/modules and copy one of the modules to a new directory
I used the bluetooth module as a starting point.

cp -r /var/www/munkireport/app/modules/bluetooth /var/www/munkireport/app/modules/sccm_status

I then went ahead and just renamed all our files in our new module

mod

Lets start with the script to gather the information, its essentially the same one I used above except we output the results to a text file on the client machine

#!/bin/bash

# Skip manual check
if [ "$1" = 'manualcheck' ]; then
	echo 'Manual check: skipping'
	exit 0
fi

# Create cache dir if it does not exit
DIR=$(dirname $0)
mkdir -p "$DIR/cache"

# Location of our output
sccm_file="/usr/local/munki/preflight.d/cache/sccm_status.txt"

# Location of the SCCM Plist
ccmpref="/var/root/Library/Preferences/com.microsoft.ccmclient.plist"

# Lets get the enrollment status
status=`defaults read $ccmpref EnrollmentStatus`

getmoreinfo() # If we are enrolled, lets pull some more info
{
echo "Status = Enrolled"

# Get the Management point
mp=`defaults read $ccmpref MP`
echo "Management_Point = $mp"

# Get the user name of the user who enrolled device
enrolluser=`defaults read $ccmpref EnrollmentUserName`
echo "Enrollment_User_Name = $enrolluser"

# Get the Enrollment Server Address
enrollserver=`defaults read $ccmpref EnrollmentServerName`
echo "Enrollment_Server_Address = $enrollserver"

# Get the last Check in time
checkin=`defaults read $ccmpref OMALastConnectTime`
echo "Last_Check_In_Time = $checkin"

# Get the Certificate Expiration date
certexp=`defaults read $ccmpref CertExpDate`
echo "Client_Certificate_Expiry_Date = $certexp"
}

# Determine if we are enrolled
if [ $status = "0" ]; 
	then
	echo "Status = Not Enrolled" > "$sccm_file"
elif [ $status = "1" ];
	then
	getmoreinfo > "$sccm_file"
fi 

exit 0

When you install munkireport on your client machine, it will go through all modules that are installed on the server in /var/www/munkireport/app/modules/ looking for install and uninstall scripts for each module.
So we need to create these install and uninstall scripts for our module

install.sh

#!/bin/bash

# bluetooth controller
CTL="${BASEURL}index.php?/module/sccm_status/"

# Get the scripts in the proper directories
${CURL} "${CTL}get_script/sccm_status_info.sh" -o "${MUNKIPATH}preflight.d/sccm_status_info.sh"

# Check exit status of curl
if [ $? = 0 ]; then
        # Make executable
        chmod a+x "${MUNKIPATH}preflight.d/sccm_status_info.sh"

        # Set preference to include this file in the preflight check
        defaults write "${PREFPATH}" ReportItems -dict-add sccm_status "${MUNKIPATH}preflight.d/cache/sccm_status.txt"

else
        echo "Failed to download all required components!"
        rm -f "${MUNKIPATH}preflight.d/sccm_status_info.sh"

        # Signal that we had an error
        ERR=1
fi

uninstall.sh

#!/bin/bash

# Remove SCCM Status script
rm -f "${MUNKIPATH}preflight.d/sccm_status_info.sh"

# Remove SCCM Status file
rm -f "${MUNKIPATH}preflight.d/cache/sccm_status.txt"

Now on a client machine, we just need run the munkireport installer and it will install our script.

# /bin/bash -c "$(curl -s --max-time 10 http://YOURMUNKIREPORTSERVER.EXAMPLE.COM/munkireport/index.php?/install)"

So thats the client side of things taken care of.

Now we need to create some php code for our module so the server knows what to do with this new data.
We need a controller and a model file. These are special files that MunkiReport knows about so we need to ensure we have these files in our module folder
and that they are named with our module name and then _controller.php and _model.php

First up the controller file

<?php 

/**
 * SCCM Agent status module class
 *
 * @package munkireport
 * @author
 **/
class sccm_status_controller extends Module_controller
{
 
 /*** Protect methods with auth! ****/
 function __construct()
 {
 // Store module path
 $this->module_path = dirname(__FILE__);
 }

 /**
 * Default method
 *
 * @author Calum Hunter
 **/
 function index()
 {
 echo "You've loaded the sccm_status module!";
 }

 
} // END class default_module

Next is the model file – this is the important bit, this is where we define our fields to create in the munki report database.

We also define what text to match from output of our script on the client and then import that information into those new data base fields

I think its pretty straight forward to see whats going on here. Just add or remove the fields as you need.

<?php
class sccm_status_model extends Model {

 function __construct($serial='')
 {
 parent::__construct('id', 'sccm_status'); //primary key, tablename
 $this->rs['id'] = '';
 $this->rs['serial_number'] = $serial; $this->rt['serial_number'] = 'VARCHAR(255) UNIQUE';
 $this->rs['agent_status'] = '';
 $this->rs['mgmt_point'] = '';
 $this->rs['enrollment_name'] = '';
 $this->rs['enrollment_server'] = ''; 
 $this->rs['last_checkin'] = '';
 $this->rs['cert_exp'] = ''; 

 // Schema version, increment when creating a db migration
 $this->schema_version = 0;
 
 // Create table if it does not exist
 $this->create_table();
 
 if ($serial)
 $this->retrieve_one('serial_number=?', $serial);
 
 $this->serial = $serial;
 
 }
 
 // ------------------------------------------------------------------------

 /**
 * Process data sent by postflight
 *
 * @param string data
 * 
 **/
 function process($data)
 { 
 // Translate network strings to db fields
 $translate = array(
 'Status = ' => 'agent_status',
 'Management_Point = ' => 'mgmt_point',
 'Enrollment_User_Name = ' => 'enrollment_name',
 'Enrollment_Server_Address = ' => 'enrollment_server',
 'Last_Check_In_Time = ' => 'last_checkin',
 'Client_Certificate_Expiry_Date = ' => 'cert_exp');

//clear any previous data we had
 foreach($translate as $search => $field) {
 $this->$field = '';
 }
 // Parse data
 foreach(explode("\n", $data) as $line) {
 // Translate standard entries
 foreach($translate as $search => $field) {
 
 if(strpos($line, $search) === 0) {
 
 $value = substr($line, strlen($search));
 
 $this->$field = $value;
 break;
 }
 } 
 
 } //end foreach explode lines
 $this->save();
 }
}

Ok now we have our module all created and ready to go. We now just need to get MunkiReport to show this information.
First we need to edit the client_details.php file to add a new line to tell it about our new module

# pico /var/www/munkireport/app/views/client/client_detail.php

Look for these lines

<li>
<a href="#ard-tab" data-toggle="tab">ARD</a>
</li>

Copy and paste an extra one in and replace the text with the name of our module so it looks like this

<li>
<a href="#sccm_status-tab" data-toggle="tab">SCCM_Status</a>
</li>

Now in the same file look for the div class definitions

<div class="tab-pane" id='bluetooth-tab'>
 <?$this->view('client/bluetooth_tab')?>
</div>

Copy and paste another one of these in there and rename it to suit

<div class="tab-pane" id='sccm_status-tab'>
 <?$this->view('client/sccm_status_tab')?>
</div>

Now we need to create the tab file that we referenced above

So again the easiest way is just to copy an existing tab file

# cp /var/www/munkireport/app/views/client/bluetooth_tab.php /var/www/munkireport/app/views/client/sccm_status_tab.php

Now we edit the contents of the new tab file and change it as needed

<?php //Initialize models needed for the table
$sccm_status = new sccm_status_model($serial_number);
?>

 <h2>sccm_status</h2>

 <table class="table table-striped">
 <tbody>
 <tr>
 <td>Enrollment Status</td>
 <td><?=$sccm_status->agent_status?></td>
 </tr>
 <tr>
 <td>Management Point</td>
 <td><?=$sccm_status->mgmt_point?></td>
 </tr>
 <tr>
 <td>Enrollment User Name</td>
 <td><?=$sccm_status->enrollment_name?></td>
 </tr>
 <tr>
 <td>Last Agent Checkin</td>
 <td><?=$sccm_status->last_checkin?></td>
 </tr>
 <tr>
 <td>Client Cert Expiry</td>
 <td><?=$sccm_status->cert_exp?></td>
 </tr>
 <tr>
 <td>Enrollment User Name</td>
 <td><?=$sccm_status->enrollment_name?></td>
 </tr>
 <tr>
 <td>Enrollment Server</td>
 <td><?=$sccm_status->enrollment_server?></td>
 </tr>
 </tbody>
 </table>

Essentially this is just defining our columns and where to pull that data from in the database.

Cool, so now it will appear in the client view. But it would be nice to have this available from the listings as well

So for that we create a list view, luckily we can again just copy an existing listing view and modify to suit

# cp /var/www/munkireport/app/views/listing/bluetooth.php /var/www/munkireport/app/views/listing/sccm_status.php

Change the fields and names to suit

<?$this->view('partials/head')?>

<? //Initialize models needed for the table
new Machine_model;
new Reportdata_model;
new Sccm_status_model;
?>

<div class="container">

 <div class="row">

 <div class="col-lg-12">
 <script type="text/javascript">

 $(document).ready(function() {

 // Get modifiers from data attribute
 var myCols = [], // Colnames
 mySort = [], // Initial sort
 hideThese = [], // Hidden columns
 col = 0; // Column counter

 $('.table th').map(function(){

 myCols.push({'mData' : $(this).data('colname')});

 if($(this).data('sort'))
 {
 mySort.push([col, $(this).data('sort')])
 }

 if($(this).data('hide'))
 {
 hideThese.push(col);
 }

 col++
 });

 oTable = $('.table').dataTable( {
 "bProcessing": true,
 "bServerSide": true,
 "sAjaxSource": "<?=url('datatables/data')?>",
 "aaSorting": mySort,
 "aoColumns": myCols,
 "aoColumnDefs": [
 { 'bVisible': false, "aTargets": hideThese }
 ],
 "fnCreatedRow": function( nRow, aData, iDataIndex ) {
 // Update name in first column to link
 var name=$('td:eq(0)', nRow).html();
 if(name == ''){name = "No Name"};
 var sn=$('td:eq(1)', nRow).html();
 var link = get_client_detail_link(name, sn, '<?=url()?>/', '#tab_sccm_status-tab');
 $('td:eq(0)', nRow).html(link);
 
 // Translate bool. todo function for any bool we find
 var status=$('td:eq(7)', nRow).html();
 status = status == 1 ? 'Yes' : 
 (status === '0' ? 'No' : '')
 $('td:eq(7)', nRow).html(status)

 }
 } );

 // Use hash as searchquery
 if(window.location.hash.substring(1))
 {
 oTable.fnFilter( decodeURIComponent(window.location.hash.substring(1)) );
 }
 
 } );
 </script>

 <h3>SCCM Agent Status report <span id="total-count" class='label label-primary'>…</span></h3>

 <table class="table table-striped table-condensed table-bordered">
 <thead>
 <tr>
 <th data-colname='machine#computer_name'>Name</th>
 <th data-colname='machine#serial_number'>Serial</th>
 <th data-colname='sccm_status#agent_status'>SCCM Agent Status</th> 
 <th data-colname='sccm_status#mgmt_point'>Management Point</th>
 <th data-colname='sccm_status#enrollment_name'>Enrollment User Name</th>
 <th data-colname='sccm_status#enrollment_server'>Enrollment Server</th>
 <th data-colname='sccm_status#last_checkin'>Last SCCM Check In</th>
 <th data-colname='sccm_status#cert_exp'>SCCM Client Cert Expiry</th>
 </tr>
 </thead>
 <tbody>
 <tr>
 <td colspan="5" class="dataTables_empty">Loading data from server</td>
 </tr>
 </tbody>
 </table>
 </div> <!-- /span 12 -->
 </div> <!-- /row -->
</div> <!-- /container -->

<?$this->view('partials/foot')?>

DONE!

Now run a preflight and postflight on our client and check out our new sccm module in Munki Report

miunki

One comment

  1. Hi
    I am stuck by:” First we need to edit the client_details.php file to add a new line to tell it about our new module” Can u help?

    Like

Leave a comment