Hi All

I have been writing a few Exchange Management scripts of late and I thought I might share the template I use to get my new scripts started. It's no master piece but hopefully might be useful to others looking to put together a new script. Let's cut to the chase and I'll get into explaining how it works.

Script Header

# +---------------------------------------------------------------------------
# | File : ScriptTemplate.ps1                                         
# | Version : 1.01                                         
# | Purpose : Do some stuff!
# | Synopsis:
# | Usage : .\ScriptTemplate.ps1 -action dostuff
# +----------------------------------------------------------------------------
# | Maintenance History                                           
# | -------------------                                           
# | Name            Date   Version  C/R  Description       
# | ----------------------------------------------------------------------------------
# | Steve Moore     2011-08-21   1.01    Here's something to get you started
# +-------------------------------------------------------------------------------
# ********************************************************************************
param ([string]$Action, [string]$Version=1.01, [bool]$Debug=$False, [string]$MailboxFile="mailboxes.csv", [string]$LogFile="$env:Computername-ScriptTemplate.log", [string]$Filter="CONTOSO", [string]$LogResults=$False, [string]$DCName="dc01.contoso.com")

The main line here is the param values. The first line of Powershell script, if you plan to use parameters, should be the param line. Param defines what parameters can be passed to the script. I find parameters pretty handy as they are a good way to make a script portable, you can override the default value of a parameter when you move the script from dev, test and prod without ever having to change code. I'll talk more about that later.

I always use an $Action parameter to tell the script what I want it to do. I usually do this as I like to use one script to achieve several functions. For example I might have one function which is to read all mail enabled users from Exchange, and then I might have a second function that sets the Extended Attribute 8 for all mail enabled users. Rather than create 2 scripts, I use two functions that are called depending on what value I pass to the $Action parameter when I initiate the script. To explain this further here is how I would call the script with an $Action of ReadUsers.

.\ScriptTemplate.ps1 -action "ReadUsers" -LogFile readusers.log -LogResults $True

In this example the $Action is ReadUsers, which I use in my Main function (we'll see later on), and the other parameters I have included are to do with whether or not I want the script to log information as it runs. Logging to a log file can be pretty handy when you are debugging, or if you just want to see the results of a script you scheduled to run at midnight.

The other cool point to make about parameters (param) in a Powershell script is that they tab complete when you pass in the options you wish to run. This is awfully handy when you haven't run the script in a while, you can type a minus character and the first letter of the argument and then hit the Tab key, Powershell will do it's magic and show you what parameters match that first character. So to explain this a little further, as I really like this feature let's use an example. If I type in the command below and hit Tab, Powershell will show me that I have two parameters starting with "L" -LogFile and -LogResults.

.\ScriptTemplate.ps1 action "ReadUsers" -Logfile

<Then>

.\ScriptTemplate.ps1 action "ReadUsers" -LogResults

The final thing to explain about the header is the [bool]$Debug=$False, [string]$MailboxFile="mailboxes.csv" example parameters. As you can guess from the examples, you can define a parameter and set a default value for the parameter. This is nice as if you choose not to execute the script without the $MailBoxFile parameter for example, the $MailBoxFile will be configured with a default value of "mailboxes.csv". Same goes for the $Debug parameter, it will be set to a boolean value of $False. I use the $Debug parameter to turn on debugging which I use to get the script to log in verbose mode. More about that later.

Script Common Functions

I write my scripts using the format:

  • Script Header
  • Script Common Functions - This section contains common functions I usually use across all my scripts. These functions will be called by functions that are further down the script.
  • Script Specific Purpose of the Script Functions - This is where I do the work that is specific to the job at hand.
  • Script Usage
  • Main - My basic main function that calls Script Purpose Functions based upon an $Action I have passed to the script

So as explained above the Script Common Functions is where I place those handy functions I use in all of my scripts. They are functions that are not specific to Exchange or AD, or whatever target source I am working with, but more Powershell generic type functions. I'll copy and paste a few here that you might get some use from.

# +---------------------------------------------------------------------------
# | FUNCTIONS
# |
# | description of function, parameters and what it returns (if any)                                                                                        
# +----------------------------------------------------------------------------

function DisplayHeader {
##-----------------------------------------------------------------------
## Function: DisplayHeader
## Purpose: Use to write a header for the script
##-----------------------------------------------------------------------
  "---------------------------------------------------------"
  "-                        CoolMail                       -"
  "-    Used to automate the process of ???????????????    -"
  "-                        '$Version'                         -"
  "---------------------------------------------------------"
 If ($LogResults) {
  OutLog "---------------------------------------------------------" 
  OutLog "-                        CoolMail                       -"
  OutLog "-    Used to automate the process of ???????????????    -"
  OutLog "-                       '$Version'                         -"
  OutLog "---------------------------------------------------------"
 }
}

function OutLog {
##-----------------------------------------------------------------------
## Function: OutLog
## Purpose: Used to write output to a log file
##-----------------------------------------------------------------------
 param ([string]$strOut, [bool]$blnNoDate)
 $strDateTime = get-date -uFormat "%d-%m-%Y %H:%M"
 If (-not ($blnNoDate)) {
  $strOutPut = ($strDateTime+ ": " + $strOut)
 } else {
  $strOutPut = $strOut
 }
 If ($LogFile -and $LogResults) {"$strOutPut" | out-file -filepath $LogFile -append}
}

function OutLn {
##-----------------------------------------------------------------------
## Function: OutLn
## Purpose: Used to write output to the screen
##-----------------------------------------------------------------------
 param ([string]$strOut, [string]$strType)

 If ($strOut -match "ERR:") {
      write-host $strOut -ForegroundColor RED -BackgroundColor WHITE
 } elseIf ($strOut -match "WARN:") {
      write-host $strOut -ForegroundColor CYAN -BackgroundColor WHITE
 } elseIf ($strOut -match "WARNING:") {
      write-host $strOut -ForegroundColor CYAN -BackgroundColor WHITE
 } elseIf ($strOut -match "INFO:") {
      write-host $strOut -ForegroundColor BLUE -BackgroundColor WHITE
 } elseIf ($strOut -match "DEBUG:") {
      write-host $strOut -ForegroundColor DARKGREEN -BackgroundColor WHITE
 } elseIf ($strOut -match "OTH:") {
      write-host $strOut -ForegroundColor DARKBLUE -BackgroundColor WHITE
 } Else {
      write-host $strOut
 }
 If ($LogResults) {OutLog $strOut}
}

DisplayHeader

The DisplayHeader function I use surprisingly enough to write a pretty banner to the page so those who are running the script get a bit of information about what script they are running. I also tend to be updating the scripts regularly, and using in many environments so I like to make sure I call out what version of the script I am running. This is mainly as it is so easy to deploy the wrong version to Production. I recommend updating your script version number whenever you make a change, it is always handy to remember what you did in each change (if your like me it is easy to forget). Not too much else to discuss here.

OutLog

The OutLog function I use to write information to the file saved in the $LogFile parameter. I only log information to $LogFile when the $LogResults is set to $True. So if you set a logfile value and turn on logging then all you have to do in your functions is to call the OutLog function.

In the OutLog function I also add a date and time value to the data you are logging so you get some time specifics around when each entry occurred. I recently included an optional parameter for this function $blnNoDate which you can use to log the data without a date time. I mainly use that option if I just want to log raw data to the file.

OutLn

The OutLn function I use to write data to the console in my scripts. It is basically a glorified write-host function that writes output in a colour coded format. All you need to do to call the function is:

OutLn "Some test you want written to the screen"

If you want to use OutLn to log in different colours you can use the following examples:

OutLn "DEBUG: Log some text when I am in debug mode (this text comes out in green)"

OutLn "ERR: Log some text when I experienced an error (this text comes out in red)"

OutLn "WARN: Log some text when I experienced a warning (this text comes out in cyan - sorry I couldn't find Cyan in my blog editor)"

OutLn "INFO: Log some text when I am just writing out info (this text comes out in blue)"

OutLn "OTH: I must have been going crazy with output in a recent script and needed another colour :) (this text comes out in dark blue)"

The OutLn function additionally logs the OutLn data to the $LogFile if logging has been enabled. This is performed by having OutLn call OutLog to write data to the logfile.

Script Common Functions (Continued)

The next two functions are used to load the Exchange and Active Directory modules. I need to probably rethink the Exchange Snap in function as it bypasses the Exchange Management Shell's Remote Powershell capabilities, which have a number advantages over a local PS session (which is what I am doing).

##-----------------------------------------------------------------------
## Function: CheckADModuleLoaded
## Purpose: This function is used to confirm the AD Powershell modules are loaded
## and if not load it
##-----------------------------------------------------------------------
function CheckADModuleLoaded
{
 param ([string]$strModuleName)

 $blnFound = $False

 Get-Module | ForEach {
  If ($_.name -match "$strModuleName") {$blnFound = $True}
 }

 If (!$blnFound) {
  import-module $strModuleName
  If ($Debug) {OutLn "Loading module $strModuleName"}
 }
}

##-----------------------------------------------------------------------
## Function: CheckExchangeModuleLoaded
## Purpose: This function is used to confirm the Exchange Powershell modules are loaded
## and if not load it
##-----------------------------------------------------------------------
function CheckExchangeModuleLoaded
{
 param ([string]$strModuleName)

 $blnFound = $False

 Get-PSSnapin | ForEach {
  If ($_.name -match "$strModuleName") {$blnFound = $True}
 }

 If (!$blnFound) {
  Add-PSSnapin $strModuleName
  If ($Debug) {OutLn "Loading module $strModuleName" "DEBUG"}
 }
}

Script Purpose Functions

The function below is just a dummy function to demonstrate how you would add your own purpose specific code into the template. This is where you put your business logic. Don't use this particular example without some rework :)

##-----------------------------------------------------------------------
## Function: Function-One
## Purpose: This function is used to do some stuff
##-----------------------------------------------------------------------
function Get-OldExchangeObjects
{
 
 $strFilter = $FILTER.ToUpper()
 OutLn "-----------------------------------------------------"
 OutLn "Checking for some stuff $strFilter"
 OutLn "-----------------------------------------------------"

 #get all results
 If ($Debug) {
  OutLn "FILTER: $FILTER"
  OutLn "DCNAME: $DCName"
  OutLn "Checking Force Upgrade"
 }

 $i = 0

 $strOrgChecker = $strFilter.ToLower()
 switch ($strOrgChecker) {
  "contoso" {
   $strOrgFilter = "contoso.com/Migration/contoso"
  }
  "fabrikam" {
   $strOrgFilter = "contoso.com/Migration/fabrikam"
  }
  default {
   # to crash script on wrong OU filter!!!
   $strOrgFilter = "contoso.com/invalidorg"
  }
 }

 #Override Org Filter if one is passed
 If ($OUFilter -ne "") {$strOrgFilter = $OUFilter}
 $objRecipients = Get-Recipient * -filter {ExchangeVersion -ne "0.10 (14.0.100.0)"} -OrganizationalUnit $strOrgFilter -Resultsize Unlimited -DomainController $DCName | Where {$_.EmailAddresses -match "@$FILTER"}

 $intCount = 0

 ForEach ($objRecipient In $objRecipients) {
  $strExternalEmail = $null

  $strPrimarySMTPAddress = $objRecipient.PrimarySMTPAddress
  $strPrimarySMTPAddressLeftOfAt = Get-LeftOfAt $strPrimarySMTPAddress
  $strFirstName = $objRecipient.FirstName
  $strLastName = $objRecipient.LastName
  $strGUID = $objRecipient.GUID
  $blnAddressPolicy = $objRecipient.EmailAddressPolicyEnabled
  $strEmailAddresses = $objRecipient.EmailAddresses
  $strDisplayName = $objRecipient.DisplayName
  $strDistinguishedName = $objRecipient.DistinguishedName
  $strCustomAttribute7 = $objRecipient.CustomAttribute7
  $strCustomAttribute8 = $objRecipient.CustomAttribute8
  $strExchangeVersion = $objRecipient.ExchangeVersion
  $strDisplayName = $objRecipient.DisplayName
  $strWhenChanged = $objRecipient.WhenChanged
  $strWhenCreated = $objRecipient.WhenCreated
  $strDistinguishedName = $objRecipient.DistinguishedName
  $strOUName = $objRecipient.OrganizationalUnit
  $strRecipientType = $objRecipient.RecipientType

  $i++

  OutLn "-----------------------------------------------------"
  If ($strPrimarySMTPAddress -eq '' -or $strPrimarySMTPAddress -eq $null) {
      OutLn "$i - Checking mail info for $strDisplayName"
  } Else {
      OutLn "$i - Checking mail info for $strPrimarySMTPAddress"
  }
  OutLn "-----------------------------------------------------"

  If ($Debug) {
   OutLn "DEBUG: Firstname: '$strFirstName' - LastName: '$strLastname' Distinguished Name: '$strDistinguishedName' $strAnalysis"
   OutLn "DEBUG: Primary SMTP Address '$strPrimarySMTPAddress'"
   OutLn "DEBUG: Email addresses $strEmailAddresses"
   OutLn "DEBUG: CustomAttribute7: '$strCustomAttribute7' - CustomAttribute8: '$strCustomAttribute8'"
   OutLn "DEBUG: GUID $strGUID"
   OutLn "DEBUG: Display Name: '$strDisplayName'"
   OutLn "DEBUG: Exchange Version: '$strExchangeVersion'"
   OutLn "DEBUG: When Created: $strWhenCreated - When Changed: $strWhenChanged"
   OutLn "DEBUG: Address Policy Enabled $blnAddressPolicy for $strPrimarySMTPAddress"

   OutLn "DEBUG: Checking email addresses for object $strPrimarySMTPAddress"
  }

  OutLn "-----------------------------------------------------"
  $intCount++
  Write-Progress -activity "Checking for objects for $Filter" -status "Checking object $strPrimarySMTPAddress" -percentcomplete (($intCount / $objRecipients.Count) * 100)
 }
}

Script Usage

I use the Usage function to display to the person running the script how I run it. This is often a section I forget to update as I extend my scripts, don't fall into that trap. It is a very handy technique for remembering how to run the script, and it also helps you to run copy and paste options you use regularly.

The other thing to note here is I would recommend you have a look at the SYNOPSIS options for correctly documenting a script. This is one of the things on my todo list as an update to the script template. Thomas Lee has an example of SYNOPSIS and the other script documentation flags usage here http://powershell.com/cs/media/p/702.aspx

##-----------------------------------------------------------------------
## Function: Usage
## Purpose: This function is used to describe how to use this script
##-----------------------------------------------------------------------
function Usage
{

@"
    Usage:
       
     .\ScriptTemplate.ps1 -Action action1 [-LogResults `$True -LogFile testing.log -Debug `$True -UpdateDir `$True -TestRun `$True -TestEmailAddress 'steve.moore@contoso.com']
     Runs the ScriptTemplate script to check for <whatever you are hoping to achieve>
     e.g. .\ScriptTemplate.ps1 -LogResults `$True -LogFile testresults.log -Debug `$True

     OR

     .\ScriptTemplate.ps1 -Action action2 [-TestRun `$True -TestEmailAddress 'stephen.moore@contoso.com' -LogResults `$True -LogFile testing.log -Debug `$True -Filter "testing"]

     OR

     .\ScriptTemplate.ps1
     Shows the usage options of ScriptTemplate
       
"@
    return
}

Main Function

The last section of my script template is the main section. It is here that I do some basic console configuration (setup pretty colours) and then check for the action passed into the script. If no action is passed to the script then I call the Usage function to show the operator how to run the script.

# +---------------------------------------------------------------------------
# | MAIN ROUTINE                                                                                        
# +----------------------------------------------------------------------------
function Main()
{

 #set console colours
 $objHost = (get-host).UI.RawUI
 $objHost.BackgroundColor = "WHITE"
 $objHost.foregroundcolor = "DARKBLUE"

 cls

 DisplayHeader
 CheckExchangeModuleLoaded "Microsoft.Exchange.Management.PowerShell.E2010"
 CheckADModuleLoaded "ActiveDirectory"

 $strTitle = "SCRIPTNAME: You can put comments up here! FILTER:$FILTER DC:$DCNAME"
 $host.ui.rawui.windowtitle = $strTitle

 If (Test-Path $LogFile) {Remove-Item -path $LogFile}

 If (($Action -eq $null) -or ($Action -eq '')) {
  Usage
  return
 }

 $strAction = $Action

 switch ($strAction) {
  "action1" {
   Function-One
  }
  "action1" {
   Function-Two
  }
  default {
   Usage
   return
  }
 }
}

#You can use $erroractionpreference to control what happens when a script error occurs. If you want the script to keep executing even when errors occur, uncomment the below line.
#$erroractionpreference = "SilentlyContinue"

#Call main and get the script running

Main

#That's it!

 

To conclude, this script template is no work of art but is a good starting point for your scripts. Like with any script you will write, a lot of it will start with something you find on the web. Hopefully my template gets you on the way to writing nice and easy to write and run scripts.

Please don't forget, test test and test all your scripts before running in prod. Use my $Debug flag to log out information and add an $Update flag (param at top of function) that controls when your script is going to make a production update. This way you can run in debug mode without updates on and see what is going to happen based upon information in your resulting $LogFile.

Happy Messaging

Steve