2009-06-17

VBscript: progress bar function

I am from time to time writing small VBscripts that doe some kind of repeating task and wanted to make some nice progress bar to show the progress. I came up with this function:
' Copyright (c) 2009 Rune Nordbøe Skillingstad 
'
' 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; version 2 dated June, 1991.
'
' 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.
'
' You should have received a copy of the GNU General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
' USA.


Option Explicit

On Error Resume Next

Dim
i

For i = 1 To 1000
Progress 1000, i
Next

Function Progress(max,prog)
Dim length, t, x
length = 68
t = int(length * (prog/max))
WScript.StdOut.Write vbCr & "|"
For x = 0 To t
WScript.StdOut.Write "-"
Next
If
t < length Then
For
x = (t + 1) To length
WScript.StdOut.Write " "
Next
End If

WScript.StdOut.Write "| " & FormatPercent(prog/max)
If t = length Then
Wscript.StdOut.WriteLine
End If
End Function


The code includes a "sample" for 1000 iterations:

video

If you are running a high number of iterations, you might want to reduce the frequency of the redrawing of the progress bar, as it will consume unwanted CPU cycles. A quick way of doing this, is to use a a modulo on the iterator counter. This example will only redraw the progress for each 10th iteration (1000/100 = 10)
max = 1000
modulo = int(max / 100)
For
i = 1 To max
If (i Mod modulo) = 0 Then
Progress max, i
End If
Next

2009-06-02

Respawn a process

I have one program that is somewhat unstable and dies from time to time (not a service).

To make sure that the program is running most of the time, I made a small VBScript using WMI to check if the process is running, and if not, respawn it.

Here is my code:
' Respawns a process if it is not running
' Can be used in scheduled tasks.
'
' Known issuses: If the program is a GUI application, the user running the
' scheduled task should be logged in.
'
' Copyright (c) 2009 Rune Nordbøe Skillingstad <rune.skillingstad@ntnu.no>
'
' 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; version 2 dated June, 1991.
'
' 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.
'
' You should have received a copy of the GNU General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
' USA.
'
Option Explicit

On Error Resume Next

Dim ProcName, ProcPath
Dim objWMIService, colItems, objShell

' Change this to your program
ProcName = "Program.exe"
ProcPath = "C:\Path"

Set objWMIService = GetObject("winmgmts:\\.\root\CIMV2")
Set colItems = objWMIService.ExecQuery("SELECT * FROM Win32_Process WHERE Name = '" & ProcName & "'")

If colItems.Count = 0 Then
Set objShell = CreateObject("WScript.Shell")
objShell.Run ProcPath & "/" & ProcName, 1, false
Set objShell = Nothing
End If

Set colItems = Nothing
Set objWMIService = Nothing


I added this as a scheduled task. Just follow the wizard, finish, edit and click advanced schedule like this screen shot:

2009-05-27

HOWTO: Install VMware Tools on Ubuntu using my script

I've previously written about my DKMS script and want to show this in action.

First of all, do this on the console, as the net driver probably will terminate your ssh connection.


My virtual demo host has very low priority, and this makes this demo a bit long...



video



I made this screen capture, using CamStudio

Regex matching email address and some benchmarking

Today I was trying to help a colleague with a regular expression (regex) for matching the most common email formats (Not quite RFC 2822, as most of the exceptions there is almost never seen).

My colleague came up with this expression:

^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$

This seems to work well, except for rare addresses having + in the local part.

My suggestion was this:

^[\w\.+-]+@(?:[a-z0-9-]{2,}\.)+[a-z0-9]{2,4}$

I wondered if mine was more efficient, and used Benchmark to try to figure this out.

My script looks like this:

#!/usr/bin/perl -w

use strict;
use Benchmark qw(:all);;

my $email = $ARGV[0]; # Supply the email address to benchmark as an argument

my $count = 1000000;

my $results = timethese($count, {'Optimized' => sub { $email =~ /^[\w\.+-]+@(?:[a-z0-9\-]{2,}\.)+[a-z0-9]{2,4}$/},
'Original' => sub { $email =~ /^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/}
});

cmpthese($results);

I first tried the most common format (I think), localpart@domain:
$ ./mailre.pl ballek@supperaadet.not
Benchmark: timing 1000000 iterations of Optimized, Original...
Optimized: 2 wallclock secs ( 1.50 usr + 0.01 sys = 1.51 CPU) @ 662251.66/s (n=1000000)
Original: 3 wallclock secs ( 2.88 usr + 0.03 sys = 2.91 CPU) @ 343642.61/s (n=1000000)
Rate Original Optimized
Original 343643/s -- -48%
Optimized 662252/s 93% --

Then the heavily used firstname.lastname@domain:

$ ./mailre.pl balle.klorin@supperaadet.not
Benchmark: timing 1000000 iterations of Optimized, Original...
Optimized: 1 wallclock secs ( 1.36 usr + 0.00 sys = 1.36 CPU) @ 735294.12/s (n=1000000)
Original: 4 wallclock secs ( 2.85 usr + 0.01 sys = 2.86 CPU) @ 349650.35/s (n=1000000)
Rate Original Optimized
Original 349650/s -- -52%
Optimized 735294/s 110% --

At last, a format used by some MTA for local aliasing (Exim and Postfix usually uses - and qmail uses +):

$ ./mailre.pl balle.klorin-list@supperaadet.not
Benchmark: timing 1000000 iterations of Optimized, Original...
Optimized: 0 wallclock secs ( 1.44 usr + 0.00 sys = 1.44 CPU) @ 694444.44/s (n=1000000)
Original: 2 wallclock secs ( 2.97 usr + 0.00 sys = 2.97 CPU) @ 336700.34/s (n=1000000)
Rate Original Optimized
Original 336700/s -- -52%
Optimized 694444/s 106% --

I was not surprised that my version was faster. The real world impact of writing slow regex might not be that big (1 million test took about 3 seconds for the unoptimized version), and for this problem, matching email addresses, the product will not become noticeably slower. On the other hand, if you write something that parses millions of lines on a frequent basis, you should try to optimize your regex.

2009-05-19

Windows: Set processor affinity

From time to time, one might want to start a process on one or more dedicated CPU/cores.

The task manager has this ability in the process view, but this only changes runtime, and you have to do this every time you start that process.

The quick solution, is to use START. From Windows 2003 Server and Windows Vista, this command has the /AFFINITY option, that will give you a persistent way of dictating the CPU affinity.

The help from START /? says this:

AFFINITY The new application will have the specified processor
affinity mask, expressed as a hexadecimal number.


The question is, how to apply this?

Well, the cores have the following hex numbers:

  1. x01

  2. x02

  3. x04

  4. x08

  5. x10

  6. x20

  7. x40

  8. x80



To start a process on CPU 1 and 2, you will have to use the hex number 3 (1+2), for 1, 2, 3 and 4 the hex number f (1+2+4+8).

Like this:

START /AFFINITY f mycommand.exe


To easily calculate these hex numbers, I use a Perl one-liner like this:

$ perl -e 'map{$hex += 2**($_-1)} @ARGV; printf "%x\n", $hex;' 1 3 5 7
55

2009-05-13

Powershell: Toggle network connection status on virtual machines in VMware (take 2)

There been some time since I wrote the script and it is now in production.

Some changes have been made, both for functionality and security reasons.

When I added the script as an scheduled task, running as a logged in user, all was fine, but when it run as a non-interactive user, it failed to log in to the Virtual Centre (VC). Therefore we made a local user, added this an administrator for the virtual machine in VC and changed the script to use a specified username and password.

While trying to debug this when running as a scheduled task, we needed some kind of logging, and a simple and well known way to do this is by email. The new version has a possibility to send status emails (configurable).

I want to be sure that only one adapter is enabled at the time, so a small check to prevent enabling both cards is built in (the $prev check). An other thing I do, was to sort the adapters so that the script first disables the active card and then enables the inactive. This way, the virtual host is without connection on both cards for a very short time, but that is more secure than it was, there both cards would be enabled at the same time (usually every second time it toggled)

The code:
# Toggle connection state on NICs on a virtual machine
#
# Usage:
# powershell -noprofile -c -nologo -noninteractive "& 'C:\path to\script\toggle.ps1'"
#
# Copyright (c) 2009 Rune Nordbøe Skillingstad <rune.skillingstad@ntnu.no>
#
# 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; version 2 dated June, 1991.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
# USA.

# Configure
# Seems like there is a problem authenticating without user/password
# when running this script uninteractive as a schedule.
$VIServer = "localhost"
$VIUser = "toggle-user"
$VIPassword = "secret"
$ComputerName = "myvirtualhost"

# Set sendmail = 1 for status emails
$sendmail = 0
$mailfrom = "Virtual Center <virtualcentre@mydomain.tld>"
$mailto = "user@mydomain.tld"
$smtpServer = "10.140.80.100"

# Add this to a schedule:
# You might want to change Execution Rights to Unrestricted (as I haven't
# signed this remote, RemoteSigned will not work)

# Magic ignoring warnings
$wpref = $WarningPreference
$WarningPreference ="SilentlyContinue"
Add-PSsnapin VMware.VimAutomation.Core
$server = Connect-VIServer -Server $VIServer -Protocol https -User $VIUser -Password $VIPassword
# Reset warings
$WarningPreference = $wpref

$body = $VIUser + " is toggling connection status on network adapters on " + $ComputerName + "`n"

if($server) {
$adapters = Get-NetworkAdapter $ComputerName

$prev = ""
# Sort so connected is disabled first
foreach($adapter in $adapters | Sort-Object @{Expression={$_.ConnectionState.Connected};Descending=$true;}) {
if(!$adapter -or $adapter.NetworkName -eq "") {
$body += "Ignoring unnamed adapter`n"
continue
}
if($prev -eq $adapter.ConnectionState.Connected) {
$body += "Detecting same status for " + $adapter.NetworkName + " as previous adapter.. ignoring`n"
} else {
if(!$adapter.ConnectionState.Connected) {
$body += "Enabling " + $adapter.NetworkName + "`n"
$ignore = Set-NetworkAdapter $adapter -Connected:$true -Confirm:$false
} else {
$body += "Disabling " + $adapter.NetworkName + "`n"
$ignore = Set-NetworkAdapter $adapter -Connected:$false -Confirm:$false
}
}
$prev = $adapter.ConnectionState.Connected
}
} else {
$body = $body + "Problems connecting to VI server"
}

if($sendmail -eq 1) {
$msg = new-object Net.Mail.MailMessage
$smtp = new-object Net.Mail.SmtpClient($smtpServer)
$msg.From = $mailfrom
$msg.To.Add($mailto)
$msg.Subject = “VMware Network Toggler”
$msg.Body = $body
$smtp.Send($msg)
}

2009-03-17

MuninLite: Included in OpenWRT and new release

I made a project called MuninLite a couple of years ago. This is a Munin node written in Bourne shell, intended on platforms missing Perl (as the regular Munin node is written in Perl). It relies on inetd for network communication and has a small subset of normal Linux plugins (like df, if_ if_err_, interupts etc), (re)written as Bourne shell plugins that is merged into the script at "compile" time.

I had almost forgotten the project, but I got a reminder this morning on the #munin (on irc.linpro.no) IRC channel. A Redpil Linpro employee wrote that OpenWRT Kamikaze 8.09 had MuninLite in its repro. What a nice surprise.

I went to the project pages on SourceForge, and got today's second surprise: there were two patches/bug in the tracker! I have not investigated why I did not receive any email about this; it might be trapped in a SPAM filter!

I had a look at the patches, both seemed sane to me, and I applied both. I also added support for ppp devices as a valid device for the if_ and if_err_ plugins.

I might have had a plan releaseing a 1.0.1 version a very long time ago, and I am not able to recall what I have been thinking. Instead of releasing this 1.0.1 I bumped it to 1.0.2.

I would like to thank Jozsef Marton for his nice patches and OpenWRT for including my software.

2009-03-11

Powershell: Toggle network connection status on virtual machines in VMware

I have a network that is separated from the Internet, and in this network, I have a virtual machine, running Microsoft Windows Server Update Services (WSUS)

The virtual machine has two network card, and when I want to synchronize, I toggle the connection state on both adapters, disabling the internal one, and enabling the one connected to the Internet.

As we want to use Microsoft Forefront Client Security (FCS) at this site, the WSUS server should connect to the Internet couple of times a day to be able to download new virus definitions. This is not feasible to do manually, so I had to automate this.

February 2008, I attended VMWorld Europe in Cannes, and was introduced to the VMware Infrasructure Toolkit (for Windows). Until today, I have found no use for this at work, but I was going to give it a try. In fact, I had almost no experience in Powershell at all, and had to download and install Powershell first.

I used the interactive Powershell and quickly found Get-NetworkAdapter. Some trial and error due to unfamiliar syntax of the script language, but I found a way to loop through the adapters and printing out the status. What a disappointment. All I got, disregarding the connection state of the adapter, was this: VMware.VimAutomation.Client20.ConnectInfoImpl

Some minutes later, by the help of Mr Google, I found that I could use $adapter.ConnectionState.Connected as a boolean variable.

Some more minutes, fidling around in the new world of this toolkit and Powershell, I had a working script (no syntax highlighting yet, I have not found a highlighter for Powershell...) :

# Toggle connection state on NICs on a virtual machine
#
# Usage:
# powershell.exe -psc "C:\Program Files\VMware\Infrastructure\VIToolkitForWindows\vim.psc1" -noprofile -c ". \"C:\path to\script\toggleConnectedNIC.ps1\""
#
# Copyright (c) 2009 Rune Nordbøe Skillingstad <rune.skillingstad@ntnu.no>
#
# 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; version 2 dated June, 1991.
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
# USA.


# Change this to your VI server
$VIServer = "localhost"
# Change this to the virtual machine you want to toggle connection on
$ComputerName = "domain-wsus.domain.com"

# Add -User <username> -Password <password> if needed
$server = Connect-VIServer -Server $VIServer -Protocol https

$adapters = Get-NetworkAdapter $ComputerName

foreach($adapter in $adapters) {
If(!$adapter.ConnectionState.Connected) {
Write-Host "Enabling " $adapter.NetworkName
$ignore = Set-NetworkAdapter $adapter -Connected:$true -Confirm:$false
} else {
Write-Host "Disabling " $adapter.NetworkName
$ignore = Set-NetworkAdapter $adapter -Connected:$false -Confirm:$false
}
}

2009-03-10

Perl: dodo you unondoderorsostotanondod tothohe rorobobboberor lolanongoguagoge

As kids, most Scandinavians, get introduced to a language called "røverspråk" (in Norwegian) or the robber language.

Before tonight, I was unaware that this language was due to the books about Kalle Blomkvist (I have never read them) by Astrid Lindgren, but with the help of Mr Google, I found what I needed at Wikipedia :)

The reason for all this, was quite simple; I was playing around with an old translator I had written in Perl some years ago. I had not used map{}, so I quickly rewrote it as a oneliner:

$ perl -wle 'print join(" ", map{s/([bcdfghjklmnpqrstvwxz])/$1o$1/g;$_}@ARGV);' hello
hohelollole

This is a crude version, that does not translate upper case characters.

A version doing this is a bit "long" for a oneliner, but for the fun of it I will present it anyway:

$ perl -wle 'print join(" ", map{s/([bcdfghjklmnpqrstvwxz])/$1o$1/g;s/([BCDFGHJKLMNPQRSTVWXZ])/sprintf "%so%s",$1,lc($1)/ge;$_}@ARGV);' My name is Rune
Momy nonamome isos Rorunone

An untranslater is about the same amount of work:

$ perl -wle 'print join(" ", map{s/([bcdfghjklmnpqrstvwxz])o\1/$1/gi;$_}@ARGV);' Tothohe rorobobboberor lolanongoguagoge!
The robber language!

2009-03-09

VBScript: Add Forefront Client Security computers to AD security group

We are deploying Microsoft Forefront Client Security (FCS) step by step, and for deployment of policies, we have ended up using an AD security group (we did not want to deploy to an OU as this automatically installs FCS, and we need to uninstall F-Secure first).

I have made some startup scripts and a script for manuall installation that adds the computer account to the group. But, sometimes, this fails (the computer might not have network working at the moment, or someone installs the client without the script and forgets to add to the group).

I have previously shown how to monitor FSC, and by doing this dive into the OnePoint database, I was able to make a script that checks all registered computers.

The prerequisits for using this script is to have a ODBC DSN configured (this is quite simple, and the only thing I did, was to make sure it point at the right database server and uses the OnePoint database as the default database).

As before, I have uploaded the script at my website :

' Add all computers from Mom's OnePoint database to an AD group
'
' Copyright (c) 2009 Rune Nordbøe Skillingstad <rune.skillingstad@ntnu.no>
'
' Make sure you have added an ODBC DSN for your MOM database
'
' 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; version 2 dated June, 1991.
'
' 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.
'
' You should have received a copy of the GNU General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
' USA.
'
Option Explicit

On Error Resume Next

Const ADS_SCOPE_SUBTREE = 2
Dim objConnection, objRecordSet
Dim objADConnection, objADCommand, objADRecordSet
Dim objDomainGroup
Dim strDSN, strDomain, strDomainGroup
Dim isVersbose, cnt

' Change these settings to reflect your AD domain and Forefront configuration
strDSN = "OnePoint"
strDomain = "DC=domain,DC=no"
strDomainGroup = "CN=L_Computer_Forefront,OU=Groups," & strDomain
' If you want to display computers missing, set this to True
isVersbose = False

Set objConnection = CreateObject("ADODB.Connection")
Set objRecordSet = CreateObject("ADODB.Recordset")
Set objADConnection = CreateObject("ADODB.Connection")
Set objADCommand = CreateObject("ADODB.Command")
Set objDomainGroup = GetObject("LDAP://" & strDomainGroup)

cnt = 0

objADConnection.Provider = "ADsDSOObject"
objADConnection.Open "Active Directory Provider"
Set objADCommand.ActiveConnection = objADConnection

objConnection.Open "DSN=" & strDSN & ";"
objRecordSet.Open "SELECT Name FROM Computer ORDER BY Name", _
objConnection, 3, 3
objRecordSet.MoveFirst
Do Until objRecordset.EOF
objADCommand.CommandText = _
"Select ADsPath from 'LDAP://" & strDomain & "' " _
& "Where objectClass='Computer' " _
& "AND Name = '" & objRecordset.Fields("Name").Value & "'"
objADCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE

Set objADRecordSet = objADCommand.Execute
Do Until objADRecordSet.EOF
If(objDomainGroup.IsMember(objADRecordSet.Fields("ADsPath").Value) = False) Then
objDomainGroup.Add(objADRecordSet.Fields("ADsPath").Value)
If isVerbose Then
WScript.Echo objRecordset.Fields("Name").Value
End If
cnt = cnt + 1
End If
objADRecordSet.MoveNext
Loop
objRecordSet.MoveNext
Loop

objRecordSet.Close
objConnection.Close
objADRecordSet.Close
objADConnection.Close
Set objRecordset = Nothing
Set objConnection = Nothing
Set objADRecordSet = Nothing
Set objADCommand = Nothing
Set objADConnection = Nothing
Set objDomainGroup = Nothing

WScript.Echo cnt & " computers added to group"

2009-03-05

VMware Tools and DKMS

I was installing an Ubuntu LTS 8.04 server as a virtual machine on our VMware Infrastructure 3 solution, and wanted to use DKMS for automating kernel module builds.

I searched the web, and found only DKMS solutions for open-vm-tools. One posting I found interesting, was Frederik Vos' nice step-by-step guide.

I had a look at the dkms.conf from open-vm-tools and made the needed changes to make a version for the official VMware Tools. After som playing around, I quickly got the need to automate this, and started building a script.

Couple of hours later, I had an automatic installation of VMware Tools and DKMS.

$ sudo ./vmware-tools-dkms.sh
Already mounted device.. umounting
Mounting /dev/scd0: Ok
Starting installation of VMware Tools version 3.5.0-143128
Removing old version of /usr/src/vmware-tools-3.5.0-143128: Ok
Removing old version of /tmp/vmware-tools-distrib: Ok
Extracting VMwareTools-3.5.0-143128: Ok
Extracting /tmp/vmware-tools-distrib/lib/modules/source/vmblock.tar: Ok
Extracting /tmp/vmware-tools-distrib/lib/modules/source/vmdesched.tar: Ok
Extracting /tmp/vmware-tools-distrib/lib/modules/source/vmhgfs.tar: Ok
Extracting /tmp/vmware-tools-distrib/lib/modules/source/vmmemctl.tar: Ok
Extracting /tmp/vmware-tools-distrib/lib/modules/source/vmxnet.tar: Ok
Generating /usr/src/vmware-tools-3.5.0-143128/dkms.conf: Ok
Removing old dkms definitions: 3.5.0-130756 3.5.0-143128
Installing linux-headers-server: Ok
Installing build-essential: Ok
Adding vmware-tools to dkms: Ok
Building kernel modules for 2.6.24-23-server: Ok
Installing kernel modules for 2.6.24-23-server: Ok
Installing vmware-tools: Ok
Done

To use this, you should download my script or you could just download the dkms.conf

Before running the script, you should start Install/Upgrade VMware Tools on your VMware Server.

This script might work on other versions of Ubuntu having DKMS and VMware Workstation and Server as well.

2009-03-04

VBScript: Is computer in DOMAIN?

I had to make a check for domain membership in a VBScript today. Some users tried to run it on private computers, that did not belong to our domain. Source

My solution is based on ADSystemInfo and is quite simple:

' Function that checks for membership in domain
'
' Copyright (c) 2009 Rune Nordbøe Skillingstad
'
' 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; version 2 dated June, 1991.
'
' 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.
'
' You should have received a copy of the GNU General Public License
' along with this program; if not, write to the Free Software
' Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
' USA.
'
Option Explicit

On Error Resume Next

If isInDomain("DOMAIN") Then
WScript.Echo "YEAH!"
Else
WScript.Echo "NOPE!"
WScript.Quit 1
End If

Function isInDomain(ByVal strDomain)
On Error Resume Next
isInDomain = False

Dim objSysInfo

Set objSysInfo = CreateObject("ADSystemInfo")
If objSysInfo.DomainShortName = strDomain Then
isInDomain = True
End If

Set objSysInfo = Nothing
End Function

2009-03-03

Monitoring Microsoft Forefront Client Security

We recently deployed Microsoft Forefront Client Security (FCS) (replacing F-Secure, but that is an other story).

We use Munin for monitoring a lot of other services, both for Linux and Windows. And I wanted to monitor Forefront in Munin as well. FCS has quite good reports using Microsoft SQL Reporting Services. We could use these reports (and will continue to use them), but personally I have to many websites for monitoring stuff, and would like to gather them on one place.

FCS uses MOM 2005, and stores all relevant status in the OnePoint database. That seemed like a good starting point. After some peeking at the OnePoint tables, I was easily able to locate where to find the information needed.

My plan was first to gather number of computers (containing both approved and unapproved clients) and if possible, some kind of "inactive" clients (not reporting for 24 hours seems like a good starting point). Using SQL Server Management Studio, I was able to make three simple SQL queries that gave me that information:
SELECT COUNT(*) FROM Computer WHERE PendingAction = 0 AND
LastHeartbeat < '2009-02-02 11:00:00'
SELECT COUNT(*) FROM Computer WHERE PendingAction = 0 AND
LastHeartbeat >= '2009-02-02 11:00:00'
SELECT COUNT(*) FROM Computer WHERE PendingAction <> 0
That seemed simple, and I should be able to put that in an Perl script and connect to the OnePoint database using ODBC. At least that was my idea. I was able to connect to the SQL Server using FreeTDS (relevant entries from my /etc/freetds/freetds.conf):
[MyHost]
host = MyHost.domain.tld
port = 1433
tds version = 7.0
I configured ODBC (I will not explain how to do this and the reason is that I ended up not using ODBC) and used DBD::ODBC, and was happily playing with my new found toy for Munin plugins.


I made a plugin and started fetching data (I did not add the inactive computers at first, but found that useful after about a day of gathering of data). I used the AREA/STACK draw like the memory usage plugin.









The next one, was to do a gathering of MOM alerts (as FCS reports warnings and errors and such there). I quickly wrote this query to use i the config of the plugin:
SELECT Level, Name FROM AlertLevel ORDER BY Level
This gives the following output:
Level       Name
----------- --------------------
10 Success
20 Information
30 Warning
40 Error
50 Critical Error
60 Security Issue
70 Service Unavailable

That was just what I needed. The next was a quite simple query as well:

SELECT al.Level, COUNT(a.AlertLevel) AS cnt
FROM Alert a, AlertLevel al
WHERE a.AlertLevel = al.Level AND a.ResolutionState <> 255
GROUP BY al.Level
This gives me something like this:
Level       cnt
----------- -----------
40 4

I decided to make this graph a percentage graph, and that might not be the best way to represent this information (it is not accurate as well; I calculate the percentage against the total number of computers, but one computer might have more than one alert). I will change this to a plain counter type graph later before submitting it to MuninExchange.

The last thing I wanted to gather, was the deployment status of management policies. And here the fun started. I was not able to find a database table that seemed to contain this information. I was able to find a table named "fcs_Profiles" that seemed to contain the policies:
SELECT Id, Name, LatestInstanceID FROM fcs_Profiles
This give something like this:
Id                                   Name    LatestInstanceID
------------------------------------ ------- ------------------------------------
53C2E807-F9E5-4EF2-A70A-229212E27DBA Default 647A4986-C80F-48BA-A40D-C64F6A591EFB

The Id is an unique identifier for a policy and LatestInstanceID is, as the name of the field says, the latest instance of this policy (will change when you edit and deploy a policy).

But where was the status for the clients? The answer was in the three tables ClassDefinition, ClassAttribute and Attributes. In ClassDefinition, I located rows that had a Name field containing Microsoft Forefront Client Security Agent:

SELECT ClassID, Name, Description
FROM ClassDefinition
WHERE Name = 'Microsoft Forefront Client Security Agent'
ClassID Name Description
------------------------------------ ----------------------------------------- ---------------------
AD3D3E91-336F-4B49-A1A8-EC42CE3F5970 Microsoft Forefront Client Security Agent Client Security Agent

This ClassID is the found in the ClassAttribute table:

SELECT ClassAttributeID, ClassAttributeName, Description
FROM ClassAttribute
WHERE ClassID = 'AD3D3E91-336F-4B49-A1A8-EC42CE3F5970'

ClassAttributeID ClassAttributeName Description
------------------------------------ ------------------------- ----------------------------
48038713-3083-48F9-9664-10DA82739E43 AM last scan time AM last scan time
7DECE2E5-5E7B-4240-A71E-1CDF09F520A6 VA Engine version VA Engine version
3FCAA357-7F6E-469F-A6BD-224012B85BF5 AM Agent VersionAM Agent Version
89AA7658-EB5A-40EE-ACD5-24BDCC2087D9 IP Address IP Address
706C42B2-E9BF-4717-A993-3D4885E21976 Computer Name Computer Name
5A3F899B-1A4B-45D1-A51D-651B28B4C38E AM (AV) Signature Version Signature Version
F01A7C7B-5BD3-4108-BEC6-70AFCD7F5B82 Alert Level Alert Level
77F83DC4-15C2-486E-B7F5-779353D42085 FQDNs Fully qualified domain names
5C916EB0-4D89-4708-A659-8AA472E3B1B8 OS Version OS Version
6FD9202A-AA2B-4923-85DC-94404BF52996 Profile ID Profile ID
0C4C3EB6-8175-4830-925A-96C2DE748589 VA Manifest version VA Manifest version
49CA01C6-79D0-4616-ADE8-A5D6B1B79BDD MAC Address MAC Address
55ADA8AF-A9EA-49A5-805A-AE17D6BB0B53 AM Engine Version Engine Version
F14C3652-A8ED-43AA-A55D-BC1AE4AA77AA VA Agent VersionVA Agent Version
82EA78F2-CFD3-4B6E-9F09-BE0F95A1F0D4 OU OU
765522BC-20C5-4F51-A2AF-CC9A3179979A VA last scan time VA last scan time
74955FE5-114A-43B5-9F4D-EE7084CABB2E AM (AS) Signature VersionSignature Version
430EDF65-EBD8-4526-B396-F53E38903DD6 Profile Instance ID Profile Instance ID

From these ClassAttributeIDs, the one named Profile ID and Profile Instance ID is the one that gives us information about the deployment status.

I put all this together in an query:

SELECT COUNT(*), a1.Value AS Profile, a2.Value AS Instance
FROM Attribute a1, Attribute a2, ClassDefinition cd,
ClassAttribute ca1, ClassAttribute ca2
WHERE ca2.ClassID = cd.ClassID
AND cd.Name = 'Microsoft Forefront Client Security Agent'
AND ca1.ClassAttributeName = 'Profile ID'
AND ca2.ClassAttributeName = 'Profile Instance ID'
AND a1.ClassAttributeID = ca1.ClassAttributeID
AND a2.ClassAttributeID = ca2.ClassAttributeID
AND a1.InstanceID = a2.InstanceID
GROUP BY a1.Value, a2.Value
ORDER BY Profile


Profile Instance
--- ------------------------------------ ------------------------------------
2 53c2e807-f9e5-4ef2-a70a-229212e27dba NULL
377 53c2e807-f9e5-4ef2-a70a-229212e27dba 647a4986-c80f-48ba-a40d-c64f6a591efb
2 53c2e807-f9e5-4ef2-a70a-229212e27dba 74e480b0-c0d8-4e2a-b7cc-2e80b60ed093
3 53c2e807-f9e5-4ef2-a70a-229212e27dba 7a62c7e0-f72e-487b-a344-1b5b295e2759
134 53c2e807-f9e5-4ef2-a70a-229212e27dba dd6f137e-7383-42d0-9200-fa301b01e4a6
11 53c2e807-f9e5-4ef2-a70a-229212e27dba e31f1095-9432-42f1-8c97-0b86c0ef5056
3 D3B75BE9-7125-4DB1-8B24-93004BD9D88E NULL
46 D3B75BE9-7125-4DB1-8B24-93004BD9D88E N/A

So, how to understand this. Well, the first thing, you might notice, is that there is a Policy that was not in the result from the fcs_Profiles query. I have two separate installations for different AD domains, and I found D3B7 5BE9-7125-4DB1-8B24-93004BD9D88E both places. This Profile ID is the "No Policy" profile. Clients that have not yet got their OU/Group/GPO policy will report this policy back to MOM. At initiation of a new client, this Attribute will be initiated with the Value "00000000-0000-0000-0000-000000000000" and this should be treated as an "Unknown Policy". The Instance gives the current version and all other values than the one from the fsc_Profiles query is regarded as old.

I now had all the queries I needed to be able to make a Munin graph, but alas, now the strange things started to happen. The ID fields in the three tables has the data type uniqueidentifier and this seems to be able to hold a very large value. It will in fact be to large for the character data type in DBD::ODBC and will be truncated. DBD::ODBC had two attributes for handling this truncation; LongTruncOk and LongReadLen,
but these seems not to ble implemented in the ODBC/FreeTDS world. This made me change to DBD::Sybase (as this will work with SQL Server using FreeTDS) as this library does hande these long character values correct. No further change was made to the code to make it work.

After a long trip into the OnePoint database, I was finally able to get all the information I needed to graph all I wanted (for this time).

To use this plugin, download it from MuninExchange and copy it to /usr/share/munin/plugins
Install all needed plugins. These packages is needed on Ubuntu:
sudo aptitude install tdsodbc libdbd-sybase-perl
Configure your /etc/freetds/freetds.conf

Run the munin-node-configure command:
$ sudo munin-node-configure --shell
ln -s /usr/share/munin/plugins/forefront_ /etc/munin/plugins/forefront_MyHost.domain.tld_computers
ln -s /usr/share/munin/plugins/forefront_ /etc/munin/plugins/forefront_MyHost
.domain.tld_deployments
ln -s /usr/share/munin/plugins/forefront_ /etc/munin/plugins/forefront_MyHost
.domain.tld_status
The command might output many other suggestions, you should only run the one you find interesting ("sudo ln -s ....."). Edit your /etc/munin/plugin-conf.d/munin-node:

[forefront_MyHost.domain.tld_*]
env.dsn MyHost
env.dbuser <DOMAIN\user>
env.dbpass <password>
The env.dsn is only needed if your freetds.conf has [<dsn>] different than the host definition. User and password can (and should be) a domain user, granted select right for the OnePoint database.

Restart your munin-node.

If the munin-node-configure does not give you any suggestions, you don't have a parsable /etc/freetds/freetds.conf. Make your own symbolic links and restart.

On your munin server, edit your /etc/munin/munin.conf:
[MyHost.domain.tld]
address <ip>
use_node_name no