Posted by & filed under Blog.

Here’s a bootstrap C function for Zabbix Discovery rules.

I’m often writing custom discovery (and standard check) functions for the Windows Zabbix agent instead of using external script. It is faster and more efficient than calling external scripts but also some challenges have been too difficult without native API access (e.g. finding the GUID of a GPT disk).

With each new function I’ve been referencing old samples for the basic conventions of Zabbix item function so I’ve taken the time to write a template and I hope it will be of benefit to you also.

If you’re creating a new C file for your function, make sure to include it in `build/win32/project/Makefile_agent.inc` or the appropriate files.

#include "common.h"
#include "sysinfo.h"
#include "log.h"
#include "zbxjson.h"

/*
 * Custom key custom.discovery
 *
 * Replace this section with the details of your custom item check.
 * 
 * Use this function as a template for custom item checks.
 * The `zbx_json_*` functions are only required for Discovery rules.
 *
 * Be sure to change all function name, parameter and output
 * field names as required.
 *
 * Your function name should be declared in `include/sysinfo.h` after line 201.
 *
 * Your item check key needs to be declared added to `parameters_specific[]` in
 * `src/libs/zbxsysinfo/win32.h` (for Windows) or equivelent.
 * 
 * Returns:
 * {
 *        "data":[
 *                {
 *                        "{#MACRO}":"Some value"]}]}
 */ 
int	CUSTOM_ITEM(AGENT_REQUEST *request, AGENT_RESULT *result)
{
    int		ret = SYSINFO_RET_FAIL;			// Request result code
    const char	*__function_name = "CUSTOM_ITEM";	// Function name for log file
    char	*param;					// Request parameter
    
    struct	zbx_json j;				// JSON response for discovery rule
    
    zabbix_log(LOG_LEVEL_DEBUG, "In %s()", __function_name);
    
    /*
     * Parse agent request
     */
    // Validate parameter count
    if (2 != request->nparam)
	goto clean;
	
    // Get and validate first parameter
    param = get_rparam(request, 0);
    if(NULL == param || '\0' == *param)
	goto clean;
	
    // Create JSON array of discovered objects
    zbx_json_init(&j, ZBX_JSON_STAT_BUF_LEN);
    zbx_json_addarray(&j, ZBX_PROTO_TAG_DATA);
    
    /*
     * DO THINGS
     * Add JSON object and properties for each discovered asset
     */
    zbx_json_addobject(&j, NULL);
    zbx_json_addstring(&j, "{#MACRO}", "Some value", ZBX_JSON_TYPE_STRING);
    zbx_json_close(&j);

    // Finalize JSON response
    zbx_json_close(&j);
    SET_STR_RESULT(result, strdup(j.buffer));
    zbx_json_free(&j);
	
    // Success?
    ret = SYSINFO_RET_OK;
    
clean:
    /*
     * Free allocated memory and handles
     */
    
    zabbix_log(LOG_LEVEL_DEBUG, "End of %s()", __function_name);
    return ret;
}

Posted by & filed under Blog.

Want to create `.gitkeep` files in all empty folders in your source code project?

Well now you can! This little bash snippet does the trick for me when executed from the root of your git initialized project:

find . -type d -empty -not -path "./.git/*" -exec touch {}/.gitkeep \;

Git tracks files, and not directories. This means any empty directories in your source project which are required for your software to run will be omitted whenever the source code is cloned via Git. To resolve this issue, Git users typically create a hidden `.gitkeep` file and commit it to source control for all each directory.

This is typically required for log file or plugin folders which may be populated during code execution.

The above bash command will create a `.gitkeep` file in all empty subdirectories (excluding the `.git/` repository database).

Posted by & filed under Blog.

Hey, here’s a quick liner to remove all members of a Distribution Group in Exchange (2007 in my test case). This came in useful for me when trying to routinely repopulate a distribution group via scheduled task. It’s not particularly fast I must apologize. Please comment if you know of any faster methods to clear a group’s membership.

Get-DistributionGroupMember -Identity 'my.group@domain.local' | Remove-DistributionGroupMember -Identity $identity -Confirm:$False;

This should only work when excuting directly from and Exchange server. If you are using a PS remote session to connect to an exchange server, you may run into the following error:

Pipeline not executed because a pipeline is already executing. Pipelines cannot be executed concurrently.

I resolved this by first caching the results of ‘Get-DistributionGroupMember’ to a variable and then piping it through to ‘Remove-DistributionGroupMember’ (thanks to advice from Mike Pfeiffer).

$identity = 'my.group@domain.local';
$members = Get-DistributionGroupMember -Identity $identity
$members | foreach { Remove-DistributionGroupMember -Identity $target -Member $_.DistinguishedName -Confirm:$False };

You may ask why I don’t just use a Dynamic DG instead of scheduling a script? In my case, the DG needs to be populated with linked mailboxes from the Exchange domain according to security group memberships in seven external domains.

Posted by & filed under Blog.

So I ran into this spot of bother today trying to establish a remote session from one server to another server in PowerShell:

[servername] Connecting to remote server failed with the following error message : The server certificate on the destination computer (servername:443) has the following errors:
The SSL certificate could not be checked for revocation. The server used to check for revocation might be unreachable.
For more information, see the about_Remote_Troubleshooting Help topic.
+ CategoryInfo : OpenError: (System.Manageme….RemoteRunspace:RemoteRunspace) [], PSRemotingTransportExc
eption
+ FullyQualifiedErrorId : PSSessionOpenFailed

I’m fairly certain its a firewall issue with the server unable to access the CA for verification, but in this case I don’t care; I just want to establish the session.

If you are experiencing the same issue, one solution is to create and pass a new PSSessionOption object that specifies that all certificate checks should be bypassed.

Here’s how:

$sessionOption = New-PSSessionOption -SkipCACheck -SkipCNCheck -SkipRevocationCheck
$session = New-PSSession -ConnectionUri $yourUrl -Credential $credential -Authentication Basic -AllowRedirection -SessionOption $sessionOption
Import-PSSession $session

Hope it helps!

Posted by & filed under Blog.

I’ve just uploaded a significant release for WMI Studio. This version addresses a number of bugs as well as introducing the some new features.

The project is still a fair way from producing a ‘stable’ release as I work out all the variables and pitfalls in working with WMI but the release is highly usable and a fantastic productivity tool. Of course your feedback is highly appreciated and will facilitate more frequent releases.

This release introduces the following new features:

  • Intelligent query builder
  • Support for ‘ASSOCIATORS OF’ and ‘REFERENCES OF’ queries
  • Object inspector for expanding WQL results
  • Improved XML and CSV exporting of results
  • Improved scripting engine
  • Improved remote connections

Head on over to the project page, WMI Studio to download the latest version.

Posted by & filed under Blog.

Often VBScripts will be required to determine if a given user (or object) is a member of a given AD / LDAP group. For example this is useful for determining which network drives a user should mount during logon, based on their group memberships. A common issue with most script samples is that they will not account for nested group memberships.

As an example, you may have an AD group named ‘Res-Share-Public’ which is designated to grant access to your public share. The group may only contain other groups, such as departments or business groups which in turn contain the end users.

The following VBScript function will return true when testing a grandchild user account against the grandparent group, ‘Res-Share-Public’:

Function IsMember(strUserDN, strGroupDN)
	Set objGroup = GetObject(strGroupDN)
	For Each objMember in objGroup.Members
		If (LCase(objMember.Class) = "group") Then
			If (IsMember(strUserDN, objMember.AdsPath)) Then
				IsMember = True
				Exit Function
			End If
		Else
			If (objMember.distinguishedName = strUserDN) Then
				IsMember = true
				Exit Function
			End If
		End If
	Next
	Set objGroup = Nothing
	IsMember = False
End Function

The strUserDN and strGroupDN parameters must be the full Distinguished Name of the objects prefixed with “LDAP://” (ie. ADS Path format) to work correctly.

The function will return true if the User is a direct or nested child (grandchild, great-grandchild, etc) of the specified Group.

This sample is free for you to use with attribution greatly appreciated. As always, your feedback is welcomed.

Posted by & filed under Blog.

I’ve found some fantastic HTML5 <canvas> experiments at Hakim El Hattab’s site, here: http://hakim.se/experiments (Not to mention a beautifully design site).

Now having seen how well complex animations perform in modern browsers, I’m feeling much more inspired to starting animating stuff. Time to brush up on my maths.

You can see my sophisticated sketch here: http://hakim.se/experiments/html5/sketch/#41846db9

 

Posted by & filed under Blog.

I recently installed IIS7 on a Windows 7 workstation via batch file. The installation went off without a hitch but I was getting no response from http://localhost and a quick inspection of the IIS Manager snap-in showed that all my websites and application pools were stopped. I tried to start the Default App Pool but this is what I got:

Application pool cannot be started unless the Windows Process Activation Service (WAS) is running.

The Service snap-in showed that WAS was not running. When I tried to start it I got:

Error 2: The system cannot find the file specified.

Thanks to Scott Hanselman’s efforts, I found the answer in his article Fixed: “Windows Process Activation Service (WAS) is stopping because it encountered an error.”.

All that was required was to create the folder ‘C:\inetpub\temp\apppools‘. That’s it!

Once created, start the Windows Process Activation Service and World Wide Web publishing Service and you should be away! Also, make sure they are set with Startup Type, Automatic

Posted by & filed under Blog.

Most organizations will want to bypass their proxy server for local web servers (intranet, CMS, helpdesk, etc). You can manually add each new server to your exception list in your logon script or group policies or simply use this PAC script to determine if a server is local and bypass it automatically!

You can use built in commands such as isInNet() which use potentially slow DNS lookups, but this method uses Regex queries instead.

/*
 *  Web Proxy Auto-Discovery Protocol Script
 *  Written by Ryan Armstrong
 *  http://www.cavaliercoder.com
 */
function FindProxyForURL(url, host)
{
	// Proxy server in format "PROXY [proxy server]:[proxy port]"
	var proxy = "PROXY proxy.mydomain.local:8080";
	
	// Proxy Exceptions:
	var exceptions = new Array(
	
		// Non-domain hostnames (eg. intranet, helpdesk, timesheets, etc)
		/^[a-zA-Z0-9-]+$/,
		
		// Local domain hosts (eg. fileserver.mydomain.local)
		/\.mydomain\.local$/,
		
		// Local IP Addresses (ie. 192.168.0.1 - 192.168.255.254)
		/^192\.168\.\d+\.\d+$/,
		
		// Local IP Addresses (ie. 172.16.0.1 - 172.32.255.254)
		/^172\.(1[6-9])|(2[0-9])|(3[0-2])\.\d+\.\d+$/,
		
		// Local IP Addresses (ie. 10.0.0.1 - 10.255.255.254)
		/^10\.\d+\.\d+\.\d+$/,
		
		// A domain and all of its subdomains:
		/microsoft\.com$/,
		
		// A domain and NONE of its subdomains:
		/^news\.google\.com$/
	);
	
	for (i = 0; i < exceptions.length; i++) // Iterate through each exception
	{
		if (exceptions[i].test(host)) // Test regex query against hostname
		{
			return "DIRECT"; // Bypass the proxy
		}
	}
	
	return proxy; // Connect via proxy
}

With each URL request, a client's browser will execute the FindProxyForURL() function and pass it the URL string and domain host name for the request. The function needs to return a string telling the browser to connect directly, via SOCKS or via a Proxy.

Be sure to update the exception and proxy address appropriate to your needs.

The script can be found and configured automatically by most browsers if it is made available via HTTP and advertised via DHCP or DNS. Firefox and Chrome (to my knowledge) don't support the DHCP method, but most browsers support DNS.

To have your script found via DNS is must be made available at 'http://wpad.mydomain.local/wpad.dat' where 'mydomain.local', need I say it, is your local domain. To do this, I saved the script as 'wpad.dat' in the root directory of my intranet server and created a DNS CNAME record (alias) pointing to that server named 'wpad'. You must also set the MIME type of the file to 'application/x-ns-proxy-autoconfig' or the file won't download (at least from IIS in my case). See Configure MIME Type (IIS 6.0) on Technet.

If you do also want to advertise the script via DHCP (it can't hurt), simply add Option 252 to your scope options containing the URL of your script. According to this article, IE6 may require the URL to be NUL terminated.

Here's another handy tip: if you want to test the functionality of the script, you can use the following PHP script (if PHP is configured on your web server) to immediately test the result of any specified host name. Save the following script as 'index.php' in the same folder as your proxy script and browse to 'http://wpad.mydomain.local'.

<html>
	<head>
		<script language="javascript">
<?php require_once('wpad.dat'); ?>

function testHost(host)
{
	document.getElementById('result').innerHTML = FindProxyForURL('', host);
}
		</script>
	</head>
	<body>
		<h1>Proxy Config</h1>
		<p>Enter the desired hostname to discover which proxy will be used.</p>
		<input type="text" onKeyUp="testHost(this.value)" size=30/>
		<span id="result" />
	</body>
</html>

Further Reading:

Wiki page with an overview: http://en.wikipedia.org/wiki/Web_Proxy_Autodiscovery_Protocol

Microsoft examples for IE: http://technet.microsoft.com/en-us/library/dd361950.aspx

IE Proxy Result Caching: http://support.microsoft.com/kb/271361

Publishing via Apache: http://homepage.ntlworld.com/jonathan.deboynepollard/FGA/web-browser-auto-proxy-configuration.html

Best Practices: http://www.websense.com/content/support/library/web/v76/pac_file_best_practices/PAC_best_pract.aspx

Posted by & filed under Blog.

Since installing OS X Lion on a MacBook Pro at work, I’ve had a problem starting Outlook 2011. Outlook displays the splash screen for less than a second and then exits without a trace.

I did find the following error in the system logs by typing ‘tail /var/log/system.log‘ in a Terminal window.

com.apple.launchd.peruser.501[552] ([oxo-ox7a07a]).com.microsoft.Outlook[1448]) Exited with exit code: 255

I’m not sure what causes the problem but Microsoft did tell us to anticipate some issues with Outlook 2011 on OS X Lion in this article here. Fortunately this issue is easy to fix and does not seem to reoccur, but does require you to reconfigure your email accounts.

To fix the problem, you need to reset your Office Identities. There is no need to touch the plist files or create new user accounts. Simply rename ‘~/Documents/Microsoft User Data/Office 2011 Identities‘ to ‘Office 2011 Identities.backup‘ (Press ‘Return‘ to rename once selected in Finder). Please note that tilde (~) is a shortcut to your home folder in OS X and other *nix based operating systems.

Outlook should now open but will require you to recreate your mail accounts. I did so with an Exchange account so all mail items and contacts, etc were still available. I’m not sure if this would be the case with POP3 / IMAP accounts but take heart because you have a backup of your old Identities folder to pull files from! See the article Your Office Identity for more information on the Identities folder.

Please let me know if this solution works for you.