Powershell Needful Things put that in your pipeline


Windows 2008 R2 DHCP Scope Utilisation Report

Posted by Jean Louw

Just a quick little script I developed for a client this week. As you know Windows 2012 DHCP server has some pretty cool Powershell cmdlets built in, but sadly, in the case of Windows 2008 there are none.

My customer needed to know when scopes are starting to run out of addresses. I found that the DHCP server logs an event each hour after startup for all scopes that are at 80% utilisation or more.

My solution was the following script. The script is triggered by the 1020 event in the system log, and then finds the events for the last hour, and send an email to the guys to alert them.

You need to modify the script for your servers SMTP parameters and if you prefer to run the script remotely you can use -computername on the the get-eventlog cmdlet.

Admittedly, this is not the best way if you have something like SCOM in place, but as a rudimentary solution it does exactly what they need.

$eventList = @()
Get-EventLog -LogName System -After (get-date).AddHours(-1) -Source DhcpServer -InstanceId 1020 `
| foreach-Object {
$row = "" | Select ScopeAddress, Utilization, FreeIPAddresses
$row.ScopeAddress = $_.ReplacementStrings[0]
$row.Utilization= $_.ReplacementStrings[1]
$row.FreeIPAddresses = $_.ReplacementStrings[2]
$eventList += $row

$messageParameters = @{
Subject = "DHCP Scope Utilisation Report - $((Get-Date))"
Body = $eventList | Sort Utilization -Descending |
ConvertTo-Html |
From = "Scope Alert <scopealert@xyz.com>"
To = "group@xyz.com"
SmtpServer = "xxx.xxx.xxx.xxx"

Send-MailMessage @messageParameters -BodyAsHtml
Filed under: powershell, script 3 Comments

Find missing subnets in Active Directory

Posted by Jean Louw

I was doing an Active Directory health check for a customer, and one of the things I normally include, is to review the netlogon.log to find IP addresses or machines which are not linked to any subnet / site in Active Directory. These entries are listed as NO_CLIENT_SITE in the log.

This is a very manual task of logging onto each domain controller and copying the file to a central location, and then sifting through the data to remove any duplicate IP addresses etc. This task becomes very time consuming if you have a large number of domain controllers.

I decided to write a Powershell script to do the work for me. The advantage of the script, is that the data is stored in a CSV which can be imported to be sorted and manipulated to find recent entires, or remove duplicate computer names and / or IP addresses.

The code doesn't currently look for the no_client_site error specifically, it will import the entire file. The script does not rely on the Microsoft Active Directory module so you can use it with Windows 2003 domain controllers.

I hope you find this script useful and your comments and suggestions are always welcome.

$dom = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
Write-Host '..current domain is' $dom

Write-Host '..getting all domain controllers in domain'
$dcs = $dom | % { $_.DomainControllers } | Select Name
$at = ($dcs | Measure-Object).count

foreach ($dc in $dcs)
        $path = '\\' + $dc.name + '\admin$\debug\netlogon.log'
        if ((test-path $path) -eq $true)
                Write-Host "..collecting logfile from ($at)" $path
                [array]$colLogs += gc $path
            $at --

Write-Host '..combining logs'
$outFile = '.\expFile.txt'
$colLogs | Out-File $outFile

Write-Host '..importing combined log as csv'
$importString = Import-Csv $outFile -Delimiter ' ' -Header Date,Time,Domain,Error,Name,IPAddress

Write-Host '..exporting results'
$importString | select Date, Name, IPAddress | sort IPAddress -Unique | Export-Csv .\expDB.csv

Check free space on volume mount points

Posted by Jean Louw

Wow! It’s been a while since I have posted any scripts! This is mainly due to the fact that I am rather busy at work, and also working hard at completing my MCITP.

A while back a client of mine, asked if there was an easy way to use one computer to check the free space of mount points. This was a real problem for them, as the administrators would come in every morning and manually logon to each server, and use disk management to check the free space.

I was certain that there had to be a WMI object for mount points, so after a little digging, I came up with the following script:

$TotalGB = @{Name="Capacity(GB)";expression={[math]::round(($_.Capacity/ 1073741824),2)}}
$FreeGB = @{Name="FreeSpace(GB)";expression={[math]::round(($_.FreeSpace / 1073741824),2)}}
$FreePerc = @{Name="Free(%)";expression={[math]::round(((($_.FreeSpace / 1073741824)/($_.Capacity / 1073741824)) * 100),0)}}

function get-mountpoints {
$volumes = Get-WmiObject -computer $server win32_volume | Where-object {$_.DriveLetter -eq $null}
$volumes | Select SystemName, Label, $TotalGB, $FreeGB, $FreePerc | Format-Table -AutoSize

$servers = (Get-Content .\servers.txt)

foreach ($server in $servers){

The script is written to collect server names from a text file, but you could use any other method to supply you server names.

Hope this helps someone else!


Find and delete duplicate Outlook Contacts

Posted by Jean Louw

I have been fairly busy at work with little or no time to write. I did however manage to write a neat script on Friday which I thought I had to share.

My Nokia decided last week that it felt the need to duplicate all my Outlook contacts after I changed something on the ActiveSync profile. Now, normally this is not a big deal, as you can simply sort the contact items by creation date, and delete the duplicates, that is unless you mess around with them, and recreate them all from scratch. (insert curse word here)

I took one look at this problem and thought that it would be far too easy to just delete them manually. I decided to write a script to do the work for me. I have been playing a lot with the Outlook COM object lately so I already had most of the code to get this done quickly.

The script will collect all your contacts, and do a unique sort on the FullName. It then creates a temp folder under your default contacts folder, and moves the unique contacts (remember sorted by FullName only), to the temp folder.

It then dumps all the duplicates in the default contacts to a CSV and deletes them from the contacts.

At this point I stopped the script, as it made sense to check the CSV and the temp folder, and move your contacts back manually if you are happy with the results.

As usual, be very careful with this one. Automatic deletes always have the potential to end in tears. Make a backup of all your contacts before you start with the script.

I hope this script can help you.

You can download the script from here:


Determine the source site of Outlook clients on Exchange server

Posted by Jean Louw

We have been toying with the idea of centralising our Exchange environment for a while now, and as part of this project, we needed to audit our Outlook clients, to determine which source site they were connecting from.

I was tasked with this, and was able to quickly gather this information, from both the Exchange 2003 and the 2007 environment, without too much hassle.

For Exchange 2007 simply use get-logonstatistics and select the information that you need. I added some additional spice, which exports each server to a separate CSV file.

foreach ($server in get-mailboxserver){
write-host "Current server: " $server
$filename = ".\" + $server + ".csv"
Get-LogonStatistics -server $server | select UserName, ClientIPAddress | sort UserName -Unique | Export-Csv $filename

Exchange 2003 is very similar, but as you can probably guess by now, you need to use WMI.

foreach ($server in (Get-ExchangeServer | Where {$_.IsExchange2007OrLater -eq $false})){
write-host "Current server: " $server
$filename = ".\" + $server + ".csv"
Get-Wmiobject -namespace root\MicrosoftExchangeV2 -class Exchange_Logon -Computer $server | select MailboxDisplayName, ClientIP | sort MailboxDisplayName -Unique | Export-Csv $filename

My job done, I sent the CSV files of the project managers, only to find out that they thought it would be nice, to see exactly which site each IP address belonged to.

This proved to be a little more tricky, but after a few minutes of probing the Interwebs, I found a post where Shay uses nltest to get the site information for a computer.

I assimilated this into my script with a little DNS lookup to find the host name and came up with a function which will retrieve the site information for each IP address on the fly and add that to the CSV file.

function Get-ComputerSite ($ip){
Write-Host "Current IP:" $ip
$site = $null
$computer = [System.Net.Dns]::gethostentry($ip)
$site = nltest /server:$($computer.hostname) /dsgetsite
Return $site[0]

$ADSiteWMI = @{Name="ADSite";expression={Get-ComputerSite $($_.ClientIP)}}
$ADSite = @{Name="ADSite";expression={Get-ComputerSite $($_.ClientIPAddress)}}

foreach ($server in get-mailboxserver){
write-host "Current server: " $server
$filename = ".\" + $server + ".csv"
$LogonStats = Get-LogonStatistics -server $server | sort UserName -Unique
$LogonStats | select UserName, ClientIPAddress, $ADSite | Export-Csv $filename

foreach ($server in (Get-ExchangeServer | Where {$_.IsExchange2007OrLater -eq $false})){
write-host "Current server: " $server
$filename = ".\" + $server + ".csv"
$LogonStats = Get-Wmiobject -namespace root\MicrosoftExchangeV2 -class Exchange_Logon -Computer $server | sort MailboxDisplayName -Unique
$LogonStats | select MailboxDisplayName, ClientIP, $ADSiteWMI | Export-Csv $filename

This does take some time to complete on servers with many connections, but it gets the results required. I have already noticed a few issues, and the script can do with a little more refinement.

I will post these updates as soon as I get round to adding them. For now, I hope this script can help someone else with a similar problem.

The complete script can be downloaded from here:


Using SCL to prevent messages from going to Junk Mail

Posted by Jean Louw

In our environment, we have a number of email addresses which are managed by automated programs and systems and even some home grown applications.

Most of these systems use POP3 to connect to the mailboxes and download incoming email. Obviously POP3 does not give you access to subfolders like “Junk Mail”. It has come to our attention recently, that the Junk email rule has been flagging valid client messages as Junk Mail, and sending these messages to the Junk Mail folder. The result is that these instructions / client information never make it to the back office workflow systems.

To prevent this from happening, you first need to understand SCL or Spam Confidence Level.

The SCL, in a nutshell is basically a score based on a number of criteria, which determine how likely a message is to contain spam. The higher the score (maximum 9) the more confident Outlook is that the message is spam.

An awesome way to view the SCL for individual messages is to install a custom form, which displays an additional column with this information. More information about that here: http://msexchangeteam.com/archive/2004/05/26/142607.aspx

After installing the form, I needed to start sending some spam to myself. This would establish the same message is either blocked or cleared by the Transport Rule. I grabbed an obvious spam message from my Gmail account and turned it into a Powershell spambot:

$messageParameters = @{

Subject = "Vicodin ES (Hydrocodone) 650mg x 30 pills $209 -VISA- tbrkl rqg" Body = " -== The Best Painkillers available ==- Buy Hydrocodone, Vicodin ES, Codeine, Phentermin, Norco, Valiuml, Xanaxl Online You pay & we ship, Absolute NO question asked No PrescriptionNeeded (No doctor approval needed!) 100% deliver your order to your house We have been in business since 1998 This is a rare bargain online to obtain these UNIQUE products. No prior order needed. Limited supply of these hard to get pills, so hurry! "

From = "spambot9k@spam-the-planet.com"
To = "spambots@spam-the-planet.com"
Bcc = "jean.louw@domain.com"
SmtpServer = ""
Send-MailMessage @messageParameters –BodyAsHtml

Confirmed! My spam message was being trapped by the Junk Mail rule with SCL 9 and moved to the Junk Mail folder.

OK, next we needed to create the Transport Rule. Now, if you are new to Powershell / Exchange I would suggest creating the rule in the GUI, as the interface / wizard used in that process is similar to the Outlook rules wizard.

Once you have the rule created it is very easy to add additional addresses using Powershell. More about that later. For the purposes of this post, I will however create the rule using the shell.

$condition = Get-TransportRulePredicate SentTo
$condition.Addresses = @((Get-Mailbox "*jean.louw*"))
$action = Get-TransportRuleAction SetSCl $action.SclValue = "-1"
$warning = "WARNING: Adding mailboxes to this rule will prevent the Junk Mail rule from detecting possible spam."

New-TransportRule -name "Set SCL level to -1" -Conditions @($condition) -Action @($action) -Comments $warning

This script will create the rule to set the SCL for all messages to matching addresses to -1. You can replace "(Get-Mailbox "*jean.louw*")" with any expression or command, which will give you the mailboxes you need to add to the rule.

Now that we have the rule in place, we need to confirm that it is working. Yet again, I sent a control “spam” message ala spambot9000.

This time the message SCL was -1, as we predicted, and the message was not moved to Junk Mail as before.

In future, should you need to add additional email addresses to your rule, you can use the following:

$condition = Get-TransportRulePredicate SentTo
$condition.Addresses = @((Get-Mailbox "*system*"))
$condition.Addresses += @((Get-Mailbox "*louw, jean*"))
Set-TransportRule "Set SCL level to -1" -Conditions @($condition)

Remember that you have to add all of your address searches, each time, as the conditions are overwritten by set-transportrule. This is a really easy way to get around the problem of false positives in mailboxes where humans don't manage mailboxes, and are unable to notice that valid emails are being sent to Junk Mail.


Bulk export calendars from Exchange mailboxes

Posted by Jean Louw

I have never really had the need for a script like this, so when our catering manager at the office logged a support call, requesting an export of all calendars for all of our meeting rooms, I had to investigate the possibilities. He basically needed this information in order to determine how busy the individual meeting rooms were during the last year.
Following a quick, unsuccessful, Internet probing for tools or scripts that could do this, my initial feeling was to say “No sorry, can’t be done, or if we do it, it was going to be a manual task.”
A manual task, which involves, granting access to the room mailbox, logging onto the mailbox using Outlook, and exporting the calendar data to Excel. Sounds easy, but doing that a hundred times is very unproductive and torturous to say the least.

I decided to attempt to script it, and the result is something I am both proud of and ashamed of at the same time, as I am convinced there must be a better way.

It’s a very rough method, which involves the following process:

  • Get a list of rooms from a text file (as it was emailed to me). You could use get-mailbox instead.
  • Add-mailbox permission to the current user
  • Create an Outlook profile
  • Logon to the profile
  • Export the Calendar to CSV
  • Remove-MailboxPermission

I could automate most of the above, but creating new profiles on demand is something I’ve never had to do, and frankly, I had no idea how to get around this problem. After speaking to some of the developers at work, who promised me some dotnet code which could do it (which I am still waiting for might I add :)), I decided to use PRF files.

I have used PRF files very successfully in the past, on Terminal server deployments to automatically setup Outlook profiles.

I downloaded the ORK and created a PRF which I used as a template for the script. The blank PRF is attached to this post to save you the time and effort of using ORK.

The script finds and replaces the UserName and HomeServer in the PRF, although any Exchange server should resolve you to your mailbox server. It then creates a PRF and starts Outlook with the /importPRF switch. Some extra information, for anyone wanting to actually deploy or use the PRF file; the %HomeServer% variable in the PRF does not work the same way %UserName% works, if you want use the PRF, you need to specify one of your mailbox servers instead.

While Outlook is open on that profile, the script attaches to Outlook using a COM object and downloads the calendar for the specified date.

The calendar fields can be customised to suit your needs. In my case we simply needed the Start and End date, the duration, and the Organizer.

The export data is saved and the PRF is removed, sadly the swarm of profiles will remain, and you have to manually remove them. You could remove them from HKEY_CURRENT_USER\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles but I have not added that to the script.

I hope this can help you, if you ever get a freaky request like this.

The script and the PRF template can be downloaded from here:


Measure the SMTP roundtrip time to an external email address

Posted by Jean Louw

In an attempt to be more proactive about Internet email delays, whether caused by our systems, or those of our ISP, I have written a script which tests the roundtrip time on SMTP mail.
The basic idea behind the script is to send a message with a GUID, and wait for the return of that specific message. When that message returns, it measures the roundtrip time, and logs the result to disk. If the message is not returned within 30 mins, it will send you a warning message informing you of the problem.

Finally, the script creates a nice JPG with the results up to the last run.

Setting up and using this script is a little more complex than usual as it combines different technologies and resources to achieve its goal, which is to measure the roundtrip time on an actual SMTP message.
To start off, the script sends a message using a standard .NET relay. On Powershell V2 you could use send-mailmessage instead. At this point, the message is time stamped in the subject, with the current date and time. The message is also marked with a distinguishable word “SMTPPing” for the reply rule, and a random GUID, which aids in recognising the message when it returns.

I use Gmail, as my “auto-reply” robot, as I am fairly certain that their infrastructure is robust and pretty stable. If you choose to use Gmail, you will need to setup a filter, which automatically forwards all mail with the word “SMTPPing” back to your email address, and then deletes it from Gmail.

Once you are sure that the auto reply is working, you can configure the script with your SMTP email addresses and relay host.

The return messages are collected from an Outlook mailbox using MAPI. You need to customise the script for the Outlook profile it needs to logon to. More details regarding this can be found as comments inside the script.

Outlook does not like strangers poking around in your stuff, so it will constantly warn you about this. To get around this problem, and also be a little selective about what you allow, you can download an awesome free tool from MapiLab called Advanced Outlook Security.

Lastly, the script needs Excel installed, in order for the chart creation and export to JPG.

I am not sure why, but I am having problems currently closing Excel. Although I issue the command to close the application, it sometimes remains running, so lookout for excel.exe in process monitor.

As usual, your comments and suggestions are always welcome.

If you like this script and use it in production, please help me keep this project free by considering a small donation.

The script can be downloaded from here:


Updated basic Exchange queue monitor

Posted by Jean Louw

As an update to the queue monitor script, I have added a little tweak.

When the script has completed its run, it will now import the log file into Excel and create a chart displaying the message flow for the current log.

The Excel chart export seems to have a problem with the current folder. I tried using “.\” or even get-location and set location in variables, but it only works if I hard code the path.

This image can be used on a web page to display the queue information in more friendly and accessible format.Excel is set to overwrite the existing image and spreadsheet everytime the script runs.

An awesome source of Excel related information can be located at the Excel Cookbook. This information saved me a lot of time an effort while working with Excel.

Information regarding Excel chart types and styles can be found here.

The complete script with the Excel chart export section can be downloaded from here:


Updated: Exchange Mailbox Cleaner

Posted by Jean Louw

I am ready to call this the final version of my Exchange Mailbox Cleaner script!

I have successfully used it in production and it saved us the effort of having to find and remove these mailboxes manually.

The GUI also makes it easy to hand this function over to the administrators for future cleanup tasks.

I have added one more search query button, the “Last Logon” button. This button will look for users on the selected server where the LastLogon is equal to $null. This finds accounts which have essentially never logged on. There is a small bug though, if the user name is not unique, it seems that the last logon is unreadable and the account will also show up in the list.

This will however report an ERROR to the shell screen. Mailboxes which have not logged on will report the following warning to the shell:
WARNING: There is no data to return for the specified mailbox 'Bunny, Bugs', because it has not been logged on to.

For now, this is a manual method of verifying that the correct mailboxes will be removed. I am however looking for a way to avoid this and will post an update as soon as I have time to find the solution.

I have also permanently removed the Add-ADPermission from the Export-Mailbox section, as full mailbox access permissions are enough to export the mailbox.

I may build in a check later to see if the permissions are required before adding them.
#Add-ADPermission -Identity $actionItem -User $currentUser -Extendedrights "Send As" -whatif
#Add-ADPermission -Identity $actionItem -User $currentUser -Extendedrights "Receive As" -whatif

As always, any comments / suggestions with regards to the script are always welcome.

The utility requires the Exchange Management shell, and if launched from a Vista / Windows 7 needs to be “Run as Administrator”

This script was tested under Windows 7 Powershell v2.

Full information and the download location for this script can be found here: