November 2007 - Posts

I wrote an article on Exchange Anti-Virus that appeared in EMO yesterday.

EMO is "Exchange Messaging Outlook" and is produced by Diane Poremsky and Slipstick.com.

If you don't receive EMO, you can access back issues at this link.

If you've never spent any time at slipstick.com - you should! Big Smile

Posted by michael | 1 comment(s)
Filed under:

Generally speaking, Outlook 2003 and Outlook 2007 have a built-in mechanism that allows a user to change their password.

In Outlook 2007, you find this (almost) hidden capability at Tools -> Options -> Other -> Advanced Options -> Custom Forms -> Password. In Outlook 2003, it is in about the same place. However, this is buried and almost impossible to find. If you want to change the user's password, and you are directly connected to the Exchange server (and in that same domain), then using Start -> Windows Security -> Change Password is much easier.

However - that capability does not work if you are using Outlook with RPC/HTTPs. The capability discussed above uses the Win32 API to effect a password change, and that does not work when you are using RPC/HTTPs.

Therefore, if you want to change your password when using RPC/HTTPs you really have three choices.

1) Call the helpdesk - the most likely scenario if you are not in a hosted environment

2) Use the control panel - most Hosted Exchange providers provide a function on a user's control panel enabling the user to change their password, using the functionality in item (3). Most large companies will implement a similar capability for their users. Small companies will likely need to implement item (3) directly or their helpdesk will do it (as in item 1).

3) Enable IISADMPWD - use KB 555071 to enable the use of a web page to allow changing the password of an active directory user.

[The Windows 2000 version of the article is KB 269082. While a Windows 2008 version of the article can be presumed, it is not available as this article is being written.]

Posted by michael | with no comments

And now for something a little different.... 

The breadth of products that Microsoft offers is amazing.

From point-of-sale products, to education products, to office products to --- amazingly -- Enterprise Project Management (EPM).

I'm getting ready to visit a client and do some work for them installing a suite of products that Microsoft calls EPM 2007. This is comprised of three products:

Project Professional 2007 (Project Pro)
Project Server 2007 (PS)
Project Portfolio Server 2007 (PPS)

With PPS, this is primarily suited to the enterprise space (where there are a metric gazillion projects to be managed and determination of project priority and risk management of those projects can be major tasks). PS and PPS are both web-based, and PS is also SharePoint based (it works fine with WSS 3.0, with additional functionality exposed when using MOSS 2007).

Project Pro differs from Project Standard primarily in that it can connect to PS and collaborate between a number of users. Project Standard is primarily a standalone application.

Project Pro can connect to PS and integrates with Outlook 2003 and Outlook 2007 via tasks and calendars to manage projects.

With PS, you can submit enterprise projects and project proposals (and promote proposals to projects) and collaborate and reports on all tasks, projects and proposals.

PPS moves up even another step to allow you to do risk management, governance, portfolio review, and other enterprise level functionality.

Interesting stuff. If you have a few minutes, sometime you should take a look at http://microsoft.com/office/projectserver

Posted by michael | with no comments
Filed under: ,

Calling PSS is a hassle. Waiting on hold, speaking with someone who may or may not have a decent command of your language, etc. etc...

Would I like a little cheese with my whine?  Wink

After lots of requests and long delays, Microsoft has made it posible for us to request most hotfixes online, using a web form.

This capability has been available to folks with Premier support agreements for some time, but has just recently been made available to "the average joe".

One must suspect that this differs from the anonymous download, because you are required to provide an e-mail address. Traditionally, we've been told that Microsoft wanted contact information for certain hotfixes in case a need arose to contact those individuals who had downloaded a hotfix - that the hotfix may have caused a significant regression failure (i.e., caused a problem worse than it fixed). Although in my approximately 10 years of requesting hotfixes, I can't ever remember a single instance of Microsoft contacting me later about a hotfix, even when I knew that a hotfix was re-released.

I've requested a number of hotfixes using this service and reception of the hotfix varies dramatically. Microsoft says "within eight hours", but I've had hotfixes sent in 10 minutes and other take three hours. I suspect that there is still manual intervention behind the scenes.

Without further ado, here is the link:

https://support.microsoft.com/contactus2/emailcontact.aspx?scid=sw;en;1410&WS=hotfix

Enjoy!

Posted by michael | with no comments
Filed under:

Exchange 2003, you say? Who is still using that? Geeked

Most people are.

I've done the research a couple of times now, so I wanted to write it down in one place. Of course, I decided to share it with you as well.

Exchange 2003 with service pack 2 has some interesting challenges when sending e-mail to another SMTP server when that "other" server implements greylisting.

Sidebar: Greylisting is an anti-spam measure. Most of the programs that actually transmit e-mail spam don't fully follow SMTP protocols. If an unexpected SMTP protocol result is received, then they drop the SMTP connection and move on to the next one. A server that is using greylisting maintains a cache of the IP addresses of servers that have sent good e-mail. When a new server connects for the first time, the greylisting server sends a SMTP protocol message that says "I'm busy - come back in a little while" - with the expectation that the message will be retried with in a few minutes.

Most spam mailers will just drop the message.

Sometimes, Exchange will somehow seem to lose the message. Yep, lose it. It doesn't show in any visible queue, it doesn't generate an NDR, etc. etc. It just seems lost - until you restart the SMTP service (generally by rebooting the Exchange server for your monthly patch updates). Then, you may get a gazillion NDRs suddenly generated from messages that were sent quite some time ago...and users start asking questions.

This can also happen with the default Windows SMTP service, after applying Windows Server 2003 service pack 2.

For Windows Server, there is a hotfix available:

On a Windows Server 2003-based SMTP gateway server, some messages may remain in the queue folder until the SMTP service is restarted
http://support.microsoft.com/default.aspx?scid=kb;EN-US;934709

For Exchange Server, your best bet is to both apply the above hotfix and to change a registry parameter known as GlitchRetrySeconds.

For a great explanation of GlitchRetrySeconds and what it does (as well as lots of other information about the SMTP queueing engine), see:

Explaining the Mysterious SMTP Advanced Queuing Engine
http://msexchangeteam.com/archive/2005/04/04/403297.aspx.

But all you really need to know is to increase the value. I personally like the value of three minutes (180 seconds). Other people prefer two minutes (120 seconds). The default value is one minute (60 seconds). Since messages are retried three time before they are actually "sent to the back of the line", one minute can be too short (a typical greylisting delay is five minutes [300 seconds]).

The downside to setting GlitchRetrySeconds too high is that it can cause a high-volume SMTP server to not send as many messages. This is rarely a problem for most installations. But because of this, the Exchange Server Analyzer Tool will report on a non-standard value for GlitchRetrySeconds:

The SMTP GlitchRetrySeconds registry value has been manually set
http://technet.microsoft.com/en-us/library/aa996601.aspx

And finally, here is how and where you modify the value:

How to Configure Glitch Retry Interval in Exchange Server 2003
http://technet.microsoft.com/en-us/library/8b43be56-48e6-400b-8014-54c95f87d1de.aspx

Whenever I hunt for the above article, it always takes me a little while - because of the difference between "interval" and "seconds" in the article title and in the registry value. Ooops. Smile

Posted by michael | 4 comment(s)
Filed under: ,

Another little script to do something that might be a little tough otherwise...

WinAMP and WMP (Windows Media Player) both have shuffle functions, but I've wanted a way to do this from the command line for a while now...someone on a mailing list asked for a way to random play so I wrote a little script for it.

You can't use WMPlayer.exe for this because it detaches from the calling shell to become a true GUI-only application. I was unable to determine a set of command line switches that would make either WMP 10 or WMP 11 behave as I wanted.

MPlayer is a GPL'ed player found on Linux/Mac/Windows. It has options galore. The Windows port ships with two programs that provide GUI interfaces to the MPlayer executable. A simple one - MPUI - and a complex one - SMPlayer. I can already tell that I may be using MPlayer instead of WMP in the future.

You can get MPlayer for Windows here:

http://www.softpedia.com/progDownload/MPlayer-for-Windows-Full-Package-Download-55997.html

(and, I'm sure, from many other places)

Enough talking, here is the script:

#
# shuffle-play.ps1
#
# Michael B. Smith
# http://TheEssentialExchange.com
#
# MPlayer is available from
# http://www.softpedia.com/progDownload/MPlayer-for-Windows-Full-Package-Download-55997.html
#

Param(
	$directory = "C:\Music",
	$extensions = ("*.mp3", "*.wma")
)

$files = get-childitem $directory -name:$true -include $extensions

if ($files.Length -le 0)
{
	write-host "No files meet spec"
	return
}

$random = new-object System.Random

while (1)
{
	$index = $random.Next(0, $files.Length - 1)
	$media = '"' + $directory + "\" + $files[$index] + '"'
	write-host $media
	&"C:\Program Files\MPlayer for Windows\MPlayer.exe" "$media" | out-null
}

 

Posted by michael | with no comments
Filed under:

A whole bunch of Exchange MVPs are gathering for our second quarterly chat to answer your questions about Exchange 2007 and Exchange 2003.

The first chat a few months ago was lots of fun and we answered literally dozens of questions.

It will be held on December 5, 2007 at 1:00 pm - 2:00 pm (Eastern Time).

To download an Outlook calendar entry for the chat: http://www.microsoft.com/communities/chats/vcs/07_1205_tn_MVP.ics

Here is the official blurb on the chat:

We invite you to attend a Q&A with the Microsoft Exchange Server MVPs. In this chat Exchange MVPs will be on hand to answer your questions about Exchange Server, Outlook and Exchange for Small Business Server. So if you are thinking of upgrading to Exchange Server 2007 or have questions about Exchange Server 2003 we hope you can join us for this informative online chat!

You can access that at: http://www.microsoft.com/technet/community/chats/default.mspx

I hope you can join us!

Posted by michael | 1 comment(s)
Filed under:

Hi folks!

I've left the employer who has hosted my blog for the last several years (at http://blogs.brnets.com/michael).

My new blog is at http://www.TheEssentialExchange.com/blogs/michael

All the content has been moved and I request that you please update your browser favorites and RSS feeds as soon as possible. I don't know how long the old site will be available.

Thanks so much and I hope to see you often at TheEssentialExchange...

Michael B. Smith
michael at TheEssentialExchange dot com

Posted by michael | with no comments
Filed under:

Originally published October 4, 2007

 

This has been in the works for awhile, but it’s finally public.

 

This hotfix is recommended for all Exchange 2007 servers. It will primarily impact CAS servers supporting multiple protocols. Eventually it will be part of a .NET service pack, but there is no schedule for the release of that at this time. Applying the hotfix could significantly reduce the memory requirement of Exchange 2007 in your environment.

 

http://support.microsoft.com/default.aspx?scid=kb;EN-US;942027

 

Unfortunately, you do have to call PSS to get the hotfix. Thankfully, it’s a free call. Alternately, you can request the hotfix online if you have a Microsoft Connect login:

 

https://connect.microsoft.com/content/content.aspx?ContentID=3705&wa=wsignin1.0&siteid=210

 

M

Posted by michael | 1 comment(s)
Filed under:

Originally published July 26, 2007

 

One of the most common reasons for e-mail delivery failures is improper DNS configuration. There are several things that are important:

1) Having an MX record (while not strictly required by RFC, most anti-spam servers now require this)
2) Having a valid rDNS (reverse DNS) record for the IP address pointed to by the MX record
3) Having a TXT record (SPFv1 or SPFv2) that is accurate (http://spf.pobox.com)

There are, of course, other things that you might want to know about a given domain. I've written a PowerShell script (dns.ps1) that interrogates everything that I want to know about a domain. It does require an additional .Net library from Lumisoft. Thankfully, this library is also free: http://www.lumisoft.ee/lswww/download/downloads/Net/Lumisoft.Net.zip

This library includes far more than the features used by this script. If you are doing TCP programming using PowerShell (or any .Net capable language) you should really investigate what this library can do for you...

I put the library in C:\Temp on my computer. If you want to place it elsewhere, update the

[Reflection.Assembly]::LoadFile("c:\temp\LumiSoft.Net.dll") | out-null

line in the script to point to the proper location of the DLL. Also, plese update the DNS servers to your local DNS servers on this line:

[Lumisoft.Net.Dns.Client.Dns_Client]::DnsServers = "216.30.183.13", "216.30.183.14"

Otherwise, this script should simply “work” for you. For example:

[PS C:\Scripts]> ./dns briworks.com

Information for briworks.com
        IP 216.30.183.90, TTL 3600

Information for www.briworks.com
        CNAME briworks.com, TTL 3600
        IP 216.30.183.90, TTL 3599

Information for mail.briworks.com
        CNAME mail.brnets.com, TTL 3600
        IP 216.30.183.81, TTL 3040

MX Information for briworks.com
        Priority 10, Host nospam.brnets.com, TTL 3600
        Information for nospam.brnets.com
                IP 216.30.183.67, TTL 3599

NS Information for briworks.com
        NS ns1.briworks.net, TTL 3600
        NS ns2.briworks.net, TTL 3600

TXT records for briworks.com
        (no text (TXT) records)

SOA record for briworks.com
        Nameserver : ns1.briworks.net
        AdminEmail : domains@briworks.net
        Serial     : 94
        Refresh    : 900
        Retry      : 600
        Expire     : 86400
        Minimum    : 3600
        TTL        : 3600

[PS C:\Scripts]>

So, does this do anything that you can't get out of nslookup? Nope. But it does it all in one command and in a lot more compact output format.

Enjoy!

Here is the script:

 

#
# ./dns.ps1
#
# This PowerShell script makes the queries that tell you whether a
# domain is setup and/or ready for doing web-hosting and/or e-mail
# hosting.
#
# Some small assumption here that the MX host and the mail host are
# separate (which is true in basic setup for anti-spam provisioning
# at Blue Ridge Internetworks.
#
# This code is provided "as-is" with no warranty nor guarantee of
# suitability for any particular use or purpose. Use at your own
# risk.
#
# Use it however you wish. If you put it in a derivative work or
# further distribute it, provide me with named credit.
#
# Michael B. Smith
# July 2007
#

#
# result codes from resolver library
#
$rcode_no_error        = 0
$rcode_format_error    = 1
$rcode_server_failure  = 2
$rcode_name_error      = 3
$rcode_not_implemented = 4
$rcode_refused         = 5

#
# query types for DNS client
#
$qtype_a     = 1
$qtype_ns    = 2
$qtype_cname = 5
$qtype_soa   = 6
$qtype_ptr   = 12
$qtype_hinfo = 13
$qtype_mx    = 15
$qtype_txt   = 16
$qtype_aaaa  = 28

function tab($count)
{
 $str = $null
 for ($i = 0; $i -lt $count; $i++)
 {
  $str += "`t"
 }

 return $str
}

function decodeAnswer($tabCount, $answer)
{
 $t = tab $tabCount

 switch ($answer.RecordType.Value__)
 {
 $qtype_a
  {
   ($t + "IP "    + $answer.IP         + ", TTL " + $answer.TTL)
  }
 $qtype_ns
  {
   ($t + "NS "    + $answer.NameServer + ", TTL " + $answer.TTL)
  }
 $qtype_cname
  {
   ($t + "CNAME " + $answer.Alias      + ", TTL " + $answer.TTL)
  }
 $qtype_soa
  {
   ($t + "Nameserver : " + $answer.NameServer)
   ($t + "AdminEmail : " + $answer.AdminEmail)
   ($t + "Serial     : " + $answer.Serial)
   ($t + "Refresh    : " + $answer.Refresh)
   ($t + "Retry      : " + $answer.Retry)
   ($t + "Expire     : " + $answer.Expire)
   ($t + "Minimum    : " + $answer.Minimum)
   ($t + "TTL        : " + $answer.TTL)
  }
 $qtype_mx
  {
   ($t + "Priority " + $answer.Preference.ToString() + ", Host " + $answer.Host +  `
    ", TTL " + $answer.TTL)
  }
 $qtype_txt
  {
   ($t + "TXT '" + $answer.Text + "', TTL " + $answer.TTL)
  }
 }
}

function decodeAnswers($tabCount, $answers, $finalStr)
{
 foreach ($answer in $answers)
 {
  decodeAnswer $tabCount $answer
 }
 if ($answers.Length -eq 0)
 {
  ((tab $tabCount) + $finalstr)
 }
 " "
}

function doQuery($queryObject, $queryString)
{
 [LumiSoft.Net.Dns.Client.DnsServerResponse]$re = $dns.Query($queryObject, $queryString)
 if ($re -and ($re.ConnectionOK -eq $true) -and ($re.ResponseCode.value__ -eq $rcode_no_error))
 {
  return $re
 }
 else
 {
  $re = $null
  write-host ("Error on '" + $queryString + "' query for " + $queryObject)
  return $null
 }
}

[Reflection.Assembly]::LoadFile("c:\temp\LumiSoft.Net.dll") | out-null

foreach ($arg in $args)
{
 [string]$domain = $arg

 " "
 [Lumisoft.Net.Dns.Client.Dns_Client]::DnsServers = "216.30.183.13", "216.30.183.14"

 $dns = new-object LumiSoft.Net.Dns.Client.Dns_Client

 $re = doQuery $domain "A"
 if ($re)
 {
  "Information for $domain"
  decodeAnswers 1 $re.Answers "(no address (A) record)"
 }

 $re = doQuery ("www." + $domain) "A"
 if ($re)
 {
  "Information for www.$domain"
  decodeAnswers 1 $re.Answers "(no address (A) record)"
 }

 $re = doQuery ("mail." + $domain) "A"
 if ($re)
 {
  "Information for mail.$domain"
  decodeAnswers 1 $re.Answers "(no address (A) record)"
 }

 $re = doQuery $domain "MX"
 if ($re)
 {
  "MX Information for $domain"
  foreach ($answer in $re.Answers)
  {
   decodeAnswer 1 $answer

   $re2 = doQuery $answer.Host "A"
   if ($re2)
   {
    ((tab 1) + "Information for " + $answer.Host)
    decodeAnswers 2 $re2.Answers "(no address record for MX record)"
   }
  }
  if ($re.Answers.Length -eq 0)
  {
   ((tab 1) + "(no mail exchanger (MX) records)")
   " "
  }
 }

 $re = doQuery $domain "NS"
 if ($re)
 {
  "NS Information for $domain"
  decodeAnswers 1 $re.Answers "(no name server (NS) records)"
 }

 $re = doQuery $domain "TXT"
 if ($re)
 {
  "TXT records for $domain"
  decodeAnswers 1 $re.Answers "(no text (TXT) records)"
 }

 $re = doQuery $domain "SOA"
 if ($re)
 {
  "SOA record for $domain"
  decodeAnswers 1 $re.Answers "(no start of authority (SOA) record)"
 }
}

Posted by michael | with no comments
Filed under:

Originally published July 3, 2007

 

When debugging mail transport issues (that is, what it takes to get a piece of e-mail from a source server to a destination server), one of the most common questions is: what happens when you do it manually?

Truthfully, doing it manually isn't difficult. But, like any machine-to-machine interface, the server you are testing expects you to be exact in the information you send - it won't be forgiving. And, if it isn't something you do often, you may forget the syntax that you should be using. Finally, the very high volume SMTP servers (such as GMail and AOL) are extraordinarily particular about spacing and capitalization.

So...we write a program to do this. Since PowerShell is my current fav, that's what I've used.

Given a particular SMTPserver, this script first ensures that the server is ping-able. Once that is verified, then a TCP port is opened to that server (by default, this is port 25, but you can change that too). Then, a small e-mail is delivered to the server, speaking the very precise SMTP dialect required by a strict interpretation of the relevant RFCs (2821 and 2822). You may completely control the source e-mail address, the destination e-mail address, the message subject and the message body.

So, while this script purports to only be a testing script, it is almost suitable for use as a full SMTP mailer, lacking only checking for return codes. That enhancement is left as an exercise to the reader. :-)  (Although there are already easier ways to do this in PowerShell, some of us like the roll-your-own concept at times...)

Generally, two parameters are sufficient for your testing purposes: SMTPserver and destAddress. So, a typical call would be:

./test-smtp.ps1 -smtpserver mail.example.com -destaddress test@example.com

Although you can certainly change the script defaults to meet your own needs.

Without further ado, here is the script:

# test-smtp.ps1
# test a remote smtp server
#
param (
 [string]$SMTPServer  = "192.168.1.1",
 [string]$destAddress = "postmaster@example.com",
 [string]$strBody     = "Subject: test`r`n`r`nSmall contents of some email.`r`n",
 [string]$serverName  = "test.example.com",
 [string]$SMTPPort    = "25",
 [string]$srcAddress  = "test@example.com",
 [Switch]$help        = $null
)

# stop executing the script on the first error
trap { break; }

#echo parameters to output
function e
{
 $str = ""
 foreach ($arg in $args)
 {
  $str += $arg
 }
 Write-Host $str
}

# network write
function n-write([System.Net.Sockets.NetworkStream]$n, [String]$s)
{
 e ">>> " $s
 $s += "`r`n"
 $arr = $s.ToCharArray()
 
 $n.Write($arr, 0, $arr.Length)
}

# network read, place input into $script:input
function n-read([System.IO.StreamReader]$n)
{
 $script:input = $n.ReadLine()
 e "<<< " $script:input
}

function test-ping (
 [string]$SMTPserver
)
{
 # if $SMTPserver doesn't resolve, we'd bomb without the 'trap' with a
 # System.Net.NetworkInformation.PingException or System.Net.Sockets.SocketException
 trap { return $false; }

 $ping = New-Object System.Net.NetworkInformation.Ping
 if ($ping)
 {
  $rslt = $ping.Send($SMTPserver)
  if ($rslt -and ($rslt.Status.ToString() –eq “Success”))
  {
   $ping = $null
   return $true
  }
  $ping = $null
 }
 return $false
}

###
### Main
###
if ($help.isPresent)
{
 "
 . $pwd\test-smtp.ps1 
Which has the following parameters and defaults:
 SMTPServer  = 192.168.1.1
 destAddress = postmaster@example.com
 strBody     = Subject: test\r\n\r\nSmall contents of some email.\r\n
 serverName  = test.example.com
 SMTPPort    = 25
 srcAddress  = test@example.com
 help        = not set
"
 return
}

# see if we can get to the server first...
if (!(test-ping $SMTPserver))
{
 e "Error: cannot access " $SMTPserver
 return
}

$tcpServer = New-Object System.Net.Sockets.TcpClient($SMTPServer, $SMTPPort)
$netStream = $tcpServer.GetStream()
$streamRead = New-Object System.IO.StreamReader($tcpServer.GetStream())

e "SMTP connection initialized to $SMTPServer on TCP port $SMTPPort. Beginning SMTP conversation."

# get the startup message from the SMTP server
n-read  $streamRead

# say hello to the remote server
n-write $netStream ("HELO " + $serverName)
n-read  $streamRead

# tell the remote server who is sending e-mail
n-write $netStream ("MAIL FROM: <" + $srcAddress + ">")
n-read  $streamRead

# tell the remote who the mail is destined to
n-write $netStream ("RCPT TO: <" + $destAddress + ">")
n-read  $streamRead

# send the data
n-write $netStream "DATA"
n-read  $streamRead

n-write $netStream $strBody
n-write $netStream "."
n-read  $streamRead

# terminate the connection
n-write $netStream "QUIT"
n-read  $streamRead

e "Mail was sent successfully."

# be nice and clean up
$streamRead.Close()
$netStream.Close()
$tcpServer.Close()

You can take this sample and modify it to do almost any SMTP task you can think of.

Posted by michael | with no comments
Filed under: ,

Originally published June 28, 2007

 

This post and the script is Exchange 2003 friendly.

As long as you point the “ExchServer” in the attached script to an Exchange 2003 server, this script can work in a mixed environment containing Exchange 2000, Exchange 2007, and Exchange 2003 servers. It will not work in an Exchange 2000 only, or an Exchange 2007 environment (in Exchange 2007, you would use the Get-MailboxStatistics cmdlet; in Exchange 2000 you would use my MAPI script).

In this post I discussed creating a report using WMI for reporting on mailbox sizes in VBscript. In an earlier post, I discussed creating a report using MAPI for reporting on mailbox sizes in VBscript.

In the current post, I discuss creating a report on mailbox sizes using WMI in PowerShell. PowerShell makes this process much simpler than VBscript - the string processing capabilities of the language are much stronger than in VBscript and access to WMI is easier and more comprehensible. There are downsides, too: 1) you may not have installed PowerShell everywhere (get off your duff and get it done! you need this!) and 2) it is a little slower (in VBscript a single connection object would be used for multiple queries - in PowerShell, a new connection object is created for every query).

That being said, all my new development is in PowerShell and I'm converting my VBscripts as fast as I reasonably can. It's a much more powerful and much more administrator-friendly environment.

 

# process-exchange-domain.ps1
# Michael B. Smith, June, 2007
#
# This script scans an Active directory, and for every user object with
# a mailbox, it reports the size of that mailbox. After all objects are
# reported, it shows a summary of the total size of all the objects.
#
Param (
    [string]$dc       = "domain-controller.example.local",
    [string]$exchange = "exchange-server.example.local"
)
#
# you can get a DC out of RootDSE, but I run this cross-domain#
# $rootDSE = [adsi] "LDAP://RootDSE"# [string]$dc = $rootDSE.dnsHostName
# $rootDSE = $null
#
[string]$dom  = "LDAP://" + $dc + "/DC=example,DC=local"
#$dom

[System.Int64]$script:totalSize      = 0
[System.Int64]$script:totalItems     = 0
[System.Int64]$script:totalDeleted   = 0
[System.Int64]$script:totalMailboxes = 0

# "(&(ObjectCategory=Person)(ObjectClass=User))"

function process-kids ([string]$dc, [string]$exch, $kid)
{
    $class = $kid.psbase.schemaClassName

    #"class = " + $class
    #"object = " + $kid.name 
    #"path = " + $kid.psbase.path

    if ($class -eq "organizationalUnit" -or $class -eq "container")
    {
        foreach ($item in $kid.psbase.children)
        {
            process-kids $dc $exch $item
        }
    }
    elseif ($class -eq "user")
    {
        #"user = " + $kid.distinguishedName
        #$qry = "LDAP://" + $dc + "/" + $kid.distinguishedName
        #$u = [adsi] $qry
        # [string]$ledn = $u.legacyExchangeDN

        [string]$ledn = $kid.legacyExchangeDN

        if ($ledn -and ($ledn.Length -gt 0))
        {
            $qry = "select * from Exchange_Mailbox where LegacyDN='" + $ledn + "'"
            $obj = gwmi -computer $exch -namespace "root/MicrosoftExchangeV2" -query "$qry"
            if ($obj)   
            {
                #[string]$str = ([string]$u.Name).PadRight(40)

                [string]$str = ([string]$kid.Name).PadRight(40)
                $str += ([System.Int64]$obj.Size).ToString("N0").PadLeft(12)
                $str += " KB"
                $str += ([System.Int32]$obj.TotalItems).ToString("N0").PadLeft(9)
                $str += "   "
                $str += ([System.Int64]$obj.DeletedMessageSizeExtended).ToString("N0").PadLeft(12)
                $str += " KB"
                $str

                $script:totalSize += ([System.Int64]$obj.Size)
                $script:totalItems += ([System.Int32]$obj.TotalItems)
                $script:totalDeleted += ([System.Int64]$obj.DeletedMessageSizeExtended)
                $script:totalMailboxes += 1

                $obj = $null   
            }   
            else   
            {
                #[string]$str = [string]$u.Name + " has no mailbox."
                #$str   
            }  
        }

        $u = $null 
    } 
    else 
    {
        # lots of funky crap in a domain that we don't care about...
        # $kid.distinguishedName  # $class 
    }
}

"Mailbox name                               Mailbox size  Item count   Deleted size"
"=================================================================================="
$domain = [adsi] $dom
foreach ($kid in $domain.psbase.children)
{
    process-kids $dc $exchange $kid
}
$domain = $null

"=================================================================================="
$str =  ($script:totalMailboxes.ToString("N0") + " mailboxes").PadRight(25)
$str += $script:totalSize.ToString("N0").PadLeft(12) + " KB"
$str += $script:totalItems.ToString("N0").PadLeft(9) + "   "
$str += $script:totalDeleted.ToString("N0").PadLeft(12) + " KB"
$str

Is anything hard about this? Nope. Absolutely not. You will however see some things that you may not be used to seeing in a PowerShell script. Pay close attention to the format strings that I pass to the ToString() method in order to format the numbers for mailbox size, item count, and deleted item size. Also, note the casts to System.Int64 when accessing WMI properties (if you don't do this, you may access truncated or incorrect values). Also, note the use of the PadLeft() and PadRight() methods for string objects. VERY useful methods for formatting, which don't see very much use.

Finally, note the way that ADSI is used. In PowerShell, the ADSI objects are really just a wrapper for System.DirectoryServices (which is a wrapper for the base ADSI that you can access in VBscript). You should note that this script iteratively scans through a domain. This does not scale. In a large domain, you would want to do a directory search for the specific objects you are interested in, instead of doing an iterative scan. (See the LDAP query immediately above the function definition for process-kids.)

For advanced information and research, you could uncomment many of the lines of this routine and learn much about your Active Directory environment.

Posted by michael | with no comments
Filed under: , ,

Originally published June 28, 2007

 

It's just so darn easy!

I needed to monitor something going on, on one of my servers. It was just a temporary need, so I didn't want to install a monitoring package (such as Servers Alive). What I needed to see is exposed as a performance counter (perfmon object, whatever you want to call it), so I figured I'd spend a few minutes seeing if I could do it in PowerShell.

Well, lo and behold, it's easy. The Microsoft MSDN page that talks about the .Net object is here.

First, you create a reference to the Performance counter:

$perf = New-Object System.Diagnostics.PerformanceCounter("MODUSADM", "Processing Queue Length", "", "zippy")

where the first parameter is the Performance Object, the second is the specific counter within the object, the third is the “instance“ of the counter, and the fourth is the name of the computer.

Note: this is a read-only reference. It's possible to get read-write references (see the Microsoft page).

Once you have the object in $perf, you simply access the data as you would any other variable:

while (1)
{
        $perf.RawValue
        Start-Sleep -seconds 1
}

and then ctrl-C it when you don't want the value displayed anymore.

Now that's easy!

It opens up loads of opportunities for new scripts...

Posted by michael | 1 comment(s)
Filed under:

Originally published April 19, 2007

 

So...if you want to find out if a server is “around” before you start doing things to it, what do you do? If you are working from your desktop, you probably will start with a “ping” and then perhaps a “telnet server smtp” - if it's an SMTP server or “telnet server 389” if it is a DC.

You can do the same thing in a program/script. While most of my older code was written in VBScript, these days I'm doing most of my new code in PowerShell, with some of it in C#. Since you can access most of the .Net Framework in PowerShell, it makes some things really easy to do.

I still have my old habits though. When I first wanted to do this, I started out with WMI - because I knew that the Win32_PingStatus class existed, and I had used it many times in VBScript...so Version 1 of the PowerShell script looks like this:

$ip =”192.168.100.81”
$qry = ('select statuscode from win32_pingstatus where address="' + $ip + '"')
$rslt = gwmi –query “$qry”
if ($rslt.StatusCode –eq 0) {
                write-host “ping worked”
}
else {
                write-host “ping failed”
}
$rslt = $null

So that get's the ping done, and it works just fine. but now I want to open a TCP port. I've written C# programs that act as SMTP clients before, so I know that the .Net namespace I needed to use is System.Net.Sockets. So, no problem:

$port = 25
$socket = new-object System.Net.Sockets.TcpClient($ip, $port)
if ($socket –eq $null) {
        write-host “couldn’t open socket to SMTP”
}
else {
        write-host “got socket to SMTP”
        $socket = $null
}

but this begs the question - if I'm using .Net for the sockets interface, mightn't there be something for pinging too? And of course, it turns out that there is. Using google against msdn.microsoft.com, I found the System.Net.NetworkInformation namespace that has a class named Ping. Excellent! So, we can rewrite our ping like this for Version 2:

$ip =”192.168.100.81”
$ping = new-object System.Net.NetworkInformation.Ping
$rslt = $ping.send($ip)
if ($rslt.status.tostring() –eq “Success”) {
        write-host “ping worked”
}
else {
        write-host “ping failed”
}
$ping = $null

Isn't that much nicer? Now, we can put both pieces of the script together, and we come up with the script below for Version 3 to complete our specified goal:

$ip =”192.168.100.81”
$ping = new-object System.Net.NetworkInformation.Ping
$rslt = $ping.send($ip)
if ($rslt.status.tostring() –eq “Success”) {
                write-host “ping worked”
                # if the ping works, try opening a socket to the SMTP port
                $port = 25
                $socket = new-object System.Net.Sockets.TcpClient($ip, $port)
                if ($socket –eq $null) {
                                write-host “couldn’t open SMTP socket”
                }
                else {
                                write-host “got socket to SMTP”
                                $socket = $null
                }
}
else {
                write-host “ping failed”
}
$ping = $null

I hope this helps someone out there!

Posted by michael | 1 comment(s)
Filed under:

Originally published April 18, 2007

 

I found Outlook 2007 impossible to use at RTM (release-to-manufacturing - when a MSFT product is first released) because it was too doggone slow with my large mailbox. I dropped back to Office 2003 (since with my job, I pretty much live in Outlook).

But MSFT released a patch to Outlook last week. Now, with the exception of e-mail composition (I always used to turn off “Use Word as your e-mail editor” - in Outlook 2007, you don't have a choice), Outloko 2007 is as fast as Outlook 2003. YAY! With Outlook 2007's enhanced search capabilities, it is now a slam-dunk top choice again.

The patch is at KB 933493. Oh, and if you were already using Outlook 2007, you don't have to update the OST or PST.

Just yesterday, MSFT released the first set of patches for Exchange 2007. As promised, Exchange 2007 patches will come out in a series of roll-ups every six to ten weeks (I think this first one is a little late - but what do I know?). The rollup is at KB 930809. Since it fixes a couple of hang/crash problems, you probably want to get it installed.

Posted by michael | with no comments
Filed under: ,
More Posts Next page »