Sunday, June 24, 2018

Use Windows WMI and Powershell to send data to Homeseer

For instance if you want to sent the average temperature or the CPU cores on your Windows PC to Homeseer virtual devices so you can trigger and action on temperature too high.
Create a user to use for updating if you do not already have one

Create a virtual device in Homeseer similar to this
Be sure and uncheck  "Do not update device last change time if device value does not change:"


Note -1 is set by script if it encounters and error getting data for the virtual

Create a script like the script below replacing
10.10.1.45 with your Homeseer IP address
wmi with the user you created
wmiPass with the password for the above user.
4570 with the ref ID of the virtual device you created

function Get-Temperature {
    $t = @( Get-WmiObject MSAcpi_ThermalZoneTemperature -Namespace "root/wmi" )
    $cores = 0
    $tempTotal=0
    foreach ($temp in $t)
    {
        $CORES += 1
        $currentTempKelvin = $temp.CurrentTemperature / 10
        $currentTempCelsius = $currentTempKelvin - 273.15
        $tempTotal += $currentTempCelsius 
        $currentTempFahrenheit = (9/5) * $currentTempCelsius + 32

        #write ($currentTempCelsius.ToString() + " C : " + $currentTempFahrenheit.ToString() + " F : " + $currentTempKelvin + "K")

    }
    return $tempTotal / $cores
}

$avgTemp=Get-Temperature


$url="http://10.10.1.45/JSON?user=wmi&pass=wmiPass&request=controldevicebyvalue&ref=4570&value=" + $avgTemp
#write ("avgTemp="+$avgTemp)
#write ("url="+$url)

(New-Object System.Net.WebClient).DownloadString($url);


My script is called cpuTemp.ps1 and is in C:\diags so I set up an task to run every hour (you can make it as often as every 5 minutes) like this.


Plus you probably want to set these

For more of a rounder picture with logging, here is a fuller script. Do not forget to change the highlighted bits.

## set these to your servers values
$Hs3ip="10.10.1.45"
$Hs3user="wmi"
$Hs3pass="wmiPass"
$tempRef="4570"
$loadRef="4577"
$pctFreeRef="4576"
$logLenRef="4593"
$updateRef="4594"
$Logfile = "C:\diags\$(gc env:computername).log"
## also update disk list in local drives at bottom

## if you need help figuring out why something is not working try
## removing # from any place you see #Write in the method with the problem.

$baseURL="http://"+$Hs3ip+"/JSON?user="+$Hs3user+"&pass="+$Hs3pass+"&request=controldevicebyvalue&ref="

## for even fancier logging see https://gist.github.com/barsv/85c93b599a763206f47aec150fb41ca0
Function Write-Log($Message) {

    $Stamp = (Get-Date).toString("yyyy/MM/dd HH:mm:ss")
    $Line = "$Stamp $Message"
    If($logfile) {
    ## to stop logging put a # in front of next line
        Add-Content $logfile -Value $Line
    }
    Else {
        Write-Output $Line
    }
}

function sendData($ref,$value) {
#write ("ref="+$ref)
#write ("value="+$value)
    $url=($baseURL+$ref+"&value="+$value)
    Write-Log ($url)
    $resp = (New-Object System.Net.WebClient).DownloadString($url);
    #write $resp
    $respObj = ConvertFrom-Json($resp)

    if (! $respObj.Name ) {
       Write-Error ("Error:"+ $resp)
       Write-Error ($url)
    }
    Write-Log ($resp)
}

function sendError($ref,$err) {
    Write-Error ("Error for:"+$ref+":"+$err)
    #Write-Log ($err)
    sendData $ref -1
}

function updateDiskFree($diskLetter,$diskRef) {
    try {
        $filter = "DeviceID='"+$diskLetter+"'"

        $disk = get-wmiobject -class "Win32_LogicalDisk" -namespace "root\CIMV2" -Filter "$filter" | Select-Object Size,FreeSpace
        $size = [math]::round($disk.Size/1GB, 2)
        $free = [math]::round($disk.FreeSpace/1GB, 2)
        $freePercent=[math]::round(($free/$size * 100), 2)
        #write ($diskLetter+" "+$diskRef+" size="+$size+" free="+$free+" free="+$freePercent+"%")

        sendData $diskRef $freePercent
    } catch {

        sendError $diskRef $_
    }
}

## other temp options:
## root\cimv2:Win32_TemperatureProbe CurrentReading
function Get-Temperature {
    try {
        $t = @( Get-WmiObject MSAcpi_ThermalZoneTemperature -Namespace "root/wmi" )
        #write $t
        $cores = 0
        $tempTotal=0
        foreach ($temp in $t)
        {
            $cores += 1
            $currentTempKelvin = $temp.CurrentTemperature / 10
            $currentTempCelsius = $currentTempKelvin - 273.15
            $tempTotal += $currentTempCelsius 
            $currentTempFahrenheit = (9/5) * $currentTempCelsius + 32

            Write-Log ("Core "+$cores+":"+$currentTempCelsius.ToString() + " C : " + $currentTempFahrenheit.ToString() + " F : " + $currentTempKelvin + "K")
        }
        sendData $tempRef ($tempTotal / $t.Count)
    } catch {
        sendError $tempRef $_
    }
}

Get-Temperature


## CPU load
try {
    $load=Get-WmiObject win32_processor | select LoadPercentage
    sendData $loadRef  $load.LoadPercentage
} catch {
    sendError $loadRef $_
}


## free RAM
try {
    $os = Get-Ciminstance Win32_OperatingSystem
    $pctFree = [math]::Round(($os.FreePhysicalMemory/$os.TotalVisibleMemorySize)*100,2)
    sendData $pctFreeRef $pctFree
} catch {
    sendError $pctFreeRef $_
}

## watch log length
try {
   #write (Get-Item $Logfile)
   $logLen = [math]::Round((Get-Item $Logfile).length / 1MB,3)
    sendData $logLenRef $logLen
} catch {
    sendError $logLenRef $_
}

## count updates pending
try {
    $UpdateSession = New-Object -ComObject Microsoft.Update.Session
    $UpdateSearcher = $UpdateSession.CreateupdateSearcher()
    ## does not want to filter on and DownloadPriority=2 or and DownloadPriority>1 so extra update is listed
    $Updates = @($UpdateSearcher.Search("IsHidden=0 and IsInstalled=0").Updates)
    $Updates| Select-Object Title,IsMandatory,IsDownloaded,RebootRequired,AutoSelection,AutoDownload,MsrcSeverity,DeploymentAction,DownloadPriority

    if ($Updates.Count > 1) {
        write ("Updates pending:"+ $Updates.Count)
    }
    sendData $updateRef $Updates.Count
} catch {
    sendError $updateRef $_
}

## local drives
updateDiskFree "C:" 4571
updateDiskFree "D:" 4572
updateDiskFree "E:" 4574
updateDiskFree "P:" 4573
## drobo
updateDiskFree "L:" 4578
##synology
updateDiskFree "Z:" 4579


Which looks like this


When I deployed to my second PC I found not even the Administrator could run scripts and had to set its policy to Bypass. See this Microsoft doc for more info on that.

Also the temperature readings are the same and unchanging on both PCs I've deployed to so far so they might not be useful despite all the posts out there claiming they are.

Tuesday, May 29, 2018

UPS monitoring

Here is an example of setting up a Raspberry Pi 3 to monitor a few UPSs.
Note you can also install on Windows, iOS and other Linux versions but for a UPS not near a PC, a Raspberry Pi 3 works a treat.

Install the apcupsd and chkconfig packages from the software manager or via yum
yum install apcupsd chkconfig -y
Note apcuspd works with many non APC brand UPSs like Cyber Power. 

This is what apcupsd has to say about multiple UPSs on a box though it uses the old style init.d structure the the current binaries for CentOS 7 for example do not.

For the first one you can use the standard files. For more than one things get a bit trickier as you need to pass parameters the built in functions do not support. Below has the added steps for adding a second, third .... UPS. Replace the 2 in apcupsd2 in each step with the number of the UPS. For first UPS just leave off the 2.
Note NISPORT 3551 is for UPS 1, 3552 for UPS 2 and so on
Note DEVICE  hiddev0 is for UPS 1, hiddev1 for UPS 2 and so on

To see which UPS is on which port use these commands
First see what ports are in use with
ls /dev/usb/hiddev*

Then for each listed run this swapping in the matching device name for hiddev0
udevadm info --attribute-walk --name=/dev/usb/hiddev0 | egrep 'manufacturer|product|serial'

mv /etc/apcupsd2/apcupsd.conf /etc/apcupsd2/apcupsd.conf.bak
vi /etc/apcupsd2/apcupsd.conf
Add lines like these
## apcupsd.conf v1.1 ##
#
#  for apcupsd2 release 3.14.12 (29 March 2014) - debian
#
# "apcupsd2" POSIX config file
UPSNAME ShopRack
UPSCABLE usb
UPSTYPE usb
DEVICE /dev/usb/hiddev1
LOCKFILE /var/lock
SCRIPTDIR /etc/apcupsd2
PWRFAILDIR /etc/apcupsd2
NOLOGINDIR /etc
ONBATTERYDELAY 6
BATTERYLEVEL 5
MINUTES 3
TIMEOUT 0
ANNOY 300
ANNOYDELAY 60
NOLOGON disable
KILLDELAY 0
NETSERVER on
NISIP 0.0.0.0
NISPORT 3552
EVENTSFILE /var/log/apcupsd2.events
EVENTSFILEMAX 10
UPSCLASS standalone
UPSMODE disable
STATTIME 0
STATFILE /var/log/apcupsd2.status
LOGSTATS off
DATATIME 0

vi /etc/init.d/apcupsd2
Replace the script lines with this. You should similarly alter /etc/init.d/apcupsd to match removing the 2 from the highlighted areas.

#!/bin/sh

### BEGIN INIT INFO

# Provides:             apcupsd2
# Required-Start:       $remote_fs $syslog
# Required-Stop:        $remote_fs $syslog
# Should-Start:         $local_fs
# Should-Stop:          $local_fs
# Default-Start:        2 3 4 5
# Default-Stop:         0 1 6
# Short-Description:    Starts apcupsd2 daemon
# Description:          apcupsd2 provides UPS power management for APC products.
### END INIT INFO

NAME=`basename $0`

PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
DAEMON=/sbin/apcupsd
CONFDIR=/etc/${NAME}
PID=/var/run/${NAME}.pid
DAEMON_OPTS="-d 9 -f ${CONFDIR}/apcupsd.conf"
CONFIG=/etc/default/apcupsd
DESC="UPS power management:${NAME}"
APCACCESS=/sbin/apcaccess

test -x $DAEMON || exit 0

test -e $CONFIG || exit 0

set -e


. $CONFIG


if [ "x$ISCONFIGURED" != "xyes" ] ;

then
        echo "Please check your configuration ISCONFIGURED in /etc/default/apcupsd"
        exit 0
fi


case "$1" in

        start)
                echo "Starting $DESC: "
                rm -f ${CONFDIR}/powerfail
                /lib/apcupsd/prestart
                PS=`ps -ef | grep ${CONFDIR}/apcupsd.conf | grep -v grep`
                if [ "$PS" = "" ]
                then
                        echo "start-stop-daemon --start --pidfile $PID --exec $DAEMON -- $DAEMON_OPTS"
                        start-stop-daemon --start --pidfile $PID --exec $DAEMON -- $DAEMON_OPTS
                        sleep 1
                        $APCACCESS status -f ${CONFDIR}/apcupsd.conf
                else
                        echo ""
                        echo "A copy of the daemon is still running.  If you just stopped it,"
                        echo "please wait about 5 seconds for it to shut down."
                        echo $PS
                        exit 0
                fi
                ;;

        status)

                $APCACCESS status -f ${CONFDIR}/apcupsd.conf
                ;;


        stop)

                echo -n "Stopping $DESC: "
                start-stop-daemon --stop --oknodo --pidfile $PID || echo "Not Running."
                #rm -f $PID
                echo "$NAME."
                ;;

        restart|force-reload)

                $0 stop
                sleep 10
                $0 start
                ;;

        *)

                N=/etc/init.d/$NAME
                echo "Usage: $N {start|stop|restart|force-reload}" >&2
                exit 1
                ;;
esac

exit 0

On CentOS 7
vi /usr/lib/systemd/system/apcupsd2.service
and add the 2 in the paths

[Unit]
Description=APC UPS Power Control Daemon for Linux
After=syslog.target
After=network-online.target

[Service]
ExecStartPre=-/bin/rm -f /etc/apcupsd2/powerfail
ExecStart=/sbin/apcupsd -b -f /etc/apcupsd2/apcupsd.conf

[Install]
WantedBy=multi-user.target


Mark ready by editing master conf file
vi /etc/default/apcupsd
Change the line
ISCONFIGURED=no
to
ISCONFIGURED=yes

Set to auto start by running
chkconfig apcupsd2 on

On CentOS 7 instead do (replace 2 as needed)
/bin/systemctl enable apcupsd2.service
firewall-cmd --permanent --add-port=3552/tcp
firewall-cmd --reload
semanage port -a -t apcupsd_port_t  -p tcp 3552
semanage port -a -t apcupsd_port_t  -p udp 3552

If iptables is enabled you might need to run this command for each port too.
iptables -I INPUT -p tcp --dport 3551 -j ACCEPT

Lastly start it.
/etc/init.d/apcupsd2 start

On CentOS 7 instead do
/bin/systemctl start apcupsd2.service

Test with
apcaccess status -f /etc/apcupsd2/apcupsd.conf
and
apcaccess -h 192.168.2.100:3552
Where 192.168.2.100 is the IP address of the system you are on

With both you should see something like this
APC      : 001,032,0743
DATE     : 2018-06-28 14:55:07 -0500
HOSTNAME : T3500
VERSION  : 3.14.14 (31 May 2016) redhat
UPSNAME  : ShopRack
CABLE    : USB Cable
DRIVER   : USB UPS Driver
UPSMODE  : Stand Alone
STARTTIME: 2018-06-28 14:52:33 -0500
MODEL    : CP 1000D
STATUS   : ONLINE
LINEV    : 123.0 Volts
LOADPCT  : 9.0 Percent
BCHARGE  : 100.0 Percent
TIMELEFT : 54.3 Minutes
MBATTCHG : 5 Percent
MINTIMEL : 3 Minutes
MAXTIME  : 0 Seconds
OUTPUTV  : 123.0 Volts
DWAKE    : 0 Seconds
LOTRANS  : 90.0 Volts
HITRANS  : 140.0 Volts
ALARMDEL : 30 Seconds
NUMXFERS : 0
TONBATT  : 0 Seconds
CUMONBATT: 0 Seconds
XOFFBATT : N/A
SELFTEST : OK
STATFLAG : 0x05000008
SERIALNO : BFE5107.B23
NOMINV   : 120 Volts
NOMPOWER : 580 Watts
END APC  : 2018-06-28 14:55:09 -0500


Other CentOS 7 quick checks / trouble shooters

Output of:
firewall-cmd --list-ports
Should contain the ports use above
3389/tcp 8443/tcp 137/tcp 138/tcp 139/tcp 445/tcp 901/tcp 3306/tcp 3552/tcp 3551/tcp

Output of:
semanage port -l | grep apcups
Should contain the ports use above
apcupsd_port_t                 tcp      3552, 3551
apcupsd_port_t                 udp      3552, 3551
Note udp should not be needed but since the apcupsd install enabled it for the first one I enabled it for the second as well to be consistent.

Lastly you might see error in /var.logs/messages like
SELinux is preventing apcupsd from read access on the file /var/log/apcupsd2.events

To fix run the commands
ausearch -c 'apcupsd' --raw | audit2allow -M my-apcupsd

semodule -i my-apcupsd.pp

For more on sorting SELinux issues look at this.

If things did not work take a look at the wiki page for help debugging and / or triggering local actions on events. Note the script they show has the line
. /lib/lsb/init-functions
Odds are nothing below that line in the script gets executed due it being overridden by 
/lib/lsb/init-functions.d/40-systemd which /lib/lsb/init-functions probably pulls in as an include. Hence the custom script above.

Now you should be able to aim your monitor at the host and port to pull in the UPS stats. For instance with Homeseer's Apcupsd plugin.

Which let's you monitor and trigger events on most of these data bits.