Improved Generic Exchange Backup Script

So after numerous emails about this, and some comments on my previous blog post back in June, I spent a couple of hours this afternoon improving the backup script (which although was written as a generic Exchange backup script, is suitable for file system, system state and more - it just depends entirely what you set the selection criteria as). I've got it running now at home great, so it's worth sharing to the bigger audience now.

The biggest request I had was for email notification if an error occurs. Sending an email is simple (honestly). However, I didn't just want to send any old boring email, I wanted to utilise some of the more advanced features capable in Outlook, and show you how you can use the extended version of CDO for Exchange (CDOEx) rather than CDO. That having been said, to keep it generic and so that this script would run on any Windows Server, I ended up using CDO and SMTP rather than the more advanced capabilities of CDOEx. I've left the CDOEx code in there if you want to go that route, and I have tested it and it does work, so is a good example if you need it. The advanced features are so that the message flag is set to highlight some action is required. It is complicated due to timezone issues, but I think they're pretty well sust, so it should work all the way from Seattle to Sydney.

Here's an example of what I mean by "advanced" - notice that the email is red, and flagged with a follow-up action

Here's the revised script. I have a question though for someone really techy which I just can't find the answer to. I'll post a followup entry shortly. Cut/Paste and save as backup.vbs. Generate your Windows Backup selection file as before, and change the series of constants at the top of the file according to your needs - recipients for error emails, SMTP servers etc. Note that I haven't tested anything except anonymous SMTP drop.

' ************************************************************************************
' * Weekly Backup Script for Exchange
' * John Howard, Microsoft UK. Created 25th June 2005
' * History:
' *  04 Nov 2005 - Added Email Sending on Failure (see inline comments)
' *
' * Feel free to use/modify for your own needs.
' * No guarantees though although it works for me 🙂
' * However, if you can do better, contact me through
' ************************************************************************************

Option Explicit
On error resume next
Const NO_ERROR = 0
Const BACKUP_PROGRAM   = "c:windowssystem32ntbackup.exe "
Const cdoImportance    = "urn:schemas:httpmail:importance"
Const cdoHigh          =  2 ' Importance
Const cdoAnonymous     = 0
Const cdoBasic         = 1
Const cdoNTLM          = 2

Const DEFAULT_SENDER   = "Backup Job <>"
Const DEFAULT_RECIP    = ""
Const DEFAULT_SUBJECT  = "Backup Failure on Exchange!"
Const SMTP_SERVER      = ""
Const SMTP_PORT        =  25
      SMTP_AUTH        = cdoAnonymous  ' Choose one of the above
Const SMTP_TIMEOUT     = 60 ' Seconds to wait for SMTP Server
Const MESSAGE_FLAG     = "URGENT: Backup Job has failed"
Const BACKUP_SHARE     = "\RemoteServerExchangeBackups"
Const BACKUP_SELECTION = "Exchange Backup Selection.bks"


Dim szYYWW                    ' Date in YYYY-WW format (Week of year)
Dim szYYMMDD                  ' Date in YYYY-MM-DD format
Dim szFlagsSelection          ' The backup selection script, prepopulated
Dim szSetDescription          ' The description of the backup set
Dim szDestinationFile         ' The destination file in the destination directory
Dim szFlagsJobName            ' Flags for the name of the job  [/j "jobname")
Dim szFlagsVerify             ' Flags for verify the backup    [yes|no]
Dim szFlagsRemoteStorage      ' Flags for remote storage       [/rs:no|yes]
Dim szFlagsHardwareCompress   ' Flags for hardware compression [/hc:off|on]
Dim szFlagsLogging            ' Flags for logging in ntbackup  [/l:f|s|n] Full SUmmary None
Dim szFlagsAppend             ' Flags for appending data       [/a] or nothing
Dim szFlagsRestrict           ' Flags for restricting access   [/r:yes|no]
Dim szFlagsType               ' Flags for backup type          [/m normal|Incremental|Differential...]
Dim szFlagsTapeName           ' Flags for name of tape
Dim oFSO                      ' File System Object to see if file already exists
Dim owShell                   ' To execute a shell command
Dim rc                        ' Return code
Dim szError                   ' If we have an error, record it in here
Dim szCommandLine             ' What we are going to run as a backup
Dim szUTC                     ' UTC Date/Time for SMTP Reply-By field

Set oFSO        = Nothing
set owShell     = Nothing
rc              = NO_ERROR  ' OK So far
szCommandLine   = ""        ' Not sure what we're running yet
szError         = ""        ' Not had an error yet

' Setup our variables
if (NO_ERROR = rc) Then
    szYYWW                   = year(now()) & " w" & formatNumber(DatePart("WW",now()))
    szYYMMDD                 = year(now()) & "-" & formatNumber(month(now())) & "-" & formatNumber(day(now()))
    szFlagsSelection         = chr(34) & "@" & BACKUP_SHARE & "" & BACKUP_SELECTION & chr(34)
    szFlagsJobName           = "/j " & chr(34) & "Exchange Backup" & chr(34)
    szFlagsVerify            = "/v:yes"        ' Verify YES|NO
    szFlagsRemoteStorage     = "/rs:no"
    szFlagsHardwareCompress  = "/hc:off"       ' Hardware compression off - this is to disk
    szFlagsLogging           = "/l:f"          ' f=full s=summary n=none
    szFlagsAppend            = "/a"            ' /a for Append or leave blank to overwrite
    szFlagsRestrict          = "/r:no"         ' no|yes Restrict access to administrators
    szFlagsTapeName          = "/t:Exchange " & szYYWW
    szDestinationFile        = "Exchange " & szYYWW & ".bkf"
    szSetDescription         = "/d " & chr(34) & "Created " & szYYMMDD & chr(34)
    szUTC                    = GetUTCSMTPDateString()
end if


' Instantiate File System Object
if (NO_ERROR = rc) Then
    Set oFSO = CreateObject("Scripting.FileSystemObject")
    if (err.number) or (oFSO is nothing) Then
        rc = -1
        szError = "Failed Creating FSO: " & err.description & " -0x" & hex(err.number)
    end if
end if

' Look to see if the file exists to determine the backup type
if (NO_ERROR = rc) Then
    if not oFSO.FileExists(BACKUP_SHARE & "" & szDestinationFile) then
        ' Normal | Copy | Differential | Incremental  Backup Type
        szFlagsType       = "/m normal "    
        szFlagsAppend     = ""  ' Don't Append if does not exist
        ' File exists, so incremental backup. We are already in Append mode
        szFlagsType       = "/m incremental "  
        szSetDescription  = "/d " & chr(34) & "Inc " & szYYMMDD & chr(34)
    end if

    ' Release File SYstem Object
    set oFSO = Nothing
end if

' Create a Shell Object to be able to run the backup executable
if (NO_ERROR = rc) Then
    Set owShell = wscript.createobject("")
    if (err.number) or (owShell is nothing) Then
        rc = -2
        szError = "Failed Creating " & err.description & " -0x" & hex(err.number)
    end if
end if

' Build the backup command and run it
if (NO_ERROR = rc) Then
    szCommandLine  = BACKUP_PROGRAM         & _
                     "backup"         & " " & _
                     szFlagsSelection      & " " & _
                     szSetDescription & " " & _
                     "/f " & chr(34) & BACKUP_SHARE & "" & szDestinationFile & chr(34) & " " & _
                     szFlagsAppend           & " " & _
                     szFlagsLogging          & " " & _
                     szFlagsVerify           & " " & _
                     szFlagsRestrict         & " " & _
                     szFlagsRemoteStorage    & " " & _
                     szFlagsHardwareCompress & " " & _
                     szFlagsType             & " " & _
                     szFlagsJobName          & " ",,True)
end if


set owShell = Nothing
if (rc) Then SendErrorEmail

Function FormatNumber(szIn)
   FormatNumber = szIn
   if len(szIn) = 1 then FormatNumber = "0" & szIn
End Function

' Function Added: JJH 04 Nov 2005
' For Sending an Email. We have the option to use send using exchange, but
' this can only be done on the Exchange server itself and requires CDOEx which
' is only installed on an Exchange Server. However CDO (CDOSys in old speak) is
' also installed on any Windows 2003 Server, and is a subset of CDOEx, we can
' use SMTP to send from any server pointing towards our Exchange Server.
' HOWEVER: It is nice to be able to get a reminder in Outlook to say that
' we need to do something rather than just an email. This requires an additional
' message header Reply-By which is in the Format (eg) Fri, 4 Nov 2005 15:23:08 -0000.
' Now, rather than mess around with timezones etc, I just use UTC. Seems to work,
' but it probably also helps that I'm in the UK so don't have to worry too much
' about timezones generally 🙂 However, appreciate many people may use this
' who are outside of UK, so best to make an effort!
' Note: We also fail safe to assume no time bias if we can't read registry
Function GetUTCSMTPDateString()
    Dim dtUTC        ' UTC Date
    Dim szUTC          ' UTC Date string in SMTP RFC Format
    Dim oShell         ' To read registry
    Dim szATBRegKey    ' Registry key for Active Time Bias
    Dim lMinutesOffset ' From UTC
    Dim rc             ' Function return
    On error resume next
    rc = 0
    szATBRegKey = "HKEY_LOCAL_MACHINESystemCurrentControlSetControlTimeZoneInformationActiveTimeBias"
    lMinutesOffset = 0

    if (NO_ERROR = rc) Then 
        set oShell = CreateObject("WScript.Shell")
        if (err.number) or (oShell = Nothing) Then rc = -1
    end if
    if (NO_ERROR = rc) Then
        lMinutesOffset = oShell.RegRead(szATBRegKey)
        if (err.number) then
     rc = -1
            lMinutesOffset = 0
        end if
    end if

    ' Regardless of error, we do the formatting (default to zero offset)
    dtUTC = dateadd("n", lMinutesOffset, now())
    szUTC = WeekdayName(Weekday(dtUTC),True) & ", " & _
            Day(dtUTC) & " " & _
            MonthName(Month(dtUTC),True) & " " & _
            Year(dtUTC) & " " & _
            FormatNumber(Hour(dtUTC)) & ":" & _
            FormatNumber(Minute(dtUTC)) & ":" & _
            FormatNumber(Second(dtUTC)) & " " & _

    set oShell = Nothing
    err.clear ' Don't pass any error back as we will have _something_
    GetUTCSMTPDateString = szUTC

End Function

' Function Added: JJH 04 Nov 2005
' There are several solutions to sending email. We _could_ use CDOEx, but we need
' to be running on an Exchange Server to do this. While you might say "But this is
' a generic Exchange backup program, so of course it's running on an Exchange Server",
' remember although I built it for Exchange backups, it's still pretty generic and
' can be used for file system backups or system state etc.
' CDOEx is not "remoteable" - it can only be instantiated on the Exchange Server
' itself. Hence, not very generic.
' BUT - CDOEx has advantages. You can use "SendUsingExchange" rather than SMTP
' which allows you to relatively easily set a reminder on the message itself.
' You would unfortunately also be limited to not just running on an Exchange
' Server, but who you are sending it _from_ must have their mailbox on that
' local server.
' Plan B: Use CDO which is present on all Windows Servers. However, to do this
' has its own problems if you also want the message to flag up if you're using
' a rich Outlook client to read the message.
' Plan B and a half is to incorporate elements of both solution (see inline comments,
' with some code commented out) but using SMTP.
' Note: I could have done this, but it just would have been _too_ easy 🙂
'Dim oMsg
'set oMsg = CreateObject("CDO.Message")
'oMsg.To = ""
'oMsg.From = ""
'oMsg.Subject = "Backup Status"
'oMsg.TextBody = "Whatever you want"
'set oMsg = Nothing

Function SendErrorEmail()

    Dim oMsg       ' Message we send if failure occurs
    Dim szFrom        ' String
    Dim szBaseFolder  ' Base folder for the current users mailbox
    Dim rc

    szFrom = ""
    rc = NO_ERROR
    set oMsg = Nothing

' REASON BEING: CDO.Person and IMailbox Interface are only implemented in CDOEx, not CDO
' Following block is to generate a full from email address so that we
' use a better display name if we are sending the email externally from
' the Exchange organisation. Not strictly necessary, but nicer.
' Also, no error checking implemented
'    Dim IMailbox      ' Mailbox Interface to CDO.Person object
'    Dim oADSInfo      ' ActiveDS.ADSystemInfo
'    Dim oPerson       ' CDO.Person
'    set oADSInfo = CreateObject("ADSystemInfo")
'    set oPerson = CreateObject("CDO.Person")
'    oPerson.DataSource.Open("LDAP://" & oADSInfo.DomainDNSName & "/" & oADSInfo.UserName)
'    szFrom = oPerson.FileAs  ' May be blank if no description in AD
'    if 0 = len(szFrom) then szFrom = "Exchange Backup Script"
'    szFrom = szFrom & " <" & oPerson.Email & ">"

    ' Need to have a default sender
    if (NO_ERROR = rc) Then
        if 0 = len(szFrom) Then szFrom = DEFAULT_SENDER
    end if

' COMMENTED OUT - AGAIN CDOEX SPECIFIC. Assuming we use code block commented
' above, we need to make sure that the users mailbox we are sending FROM (which
' is the currently logged on user) is homed on the current Exchange Server.
' The base folder will be something like file://./backofficestorage/<domain>/MBX/<User>/
'    set IMailbox = oPerson.GetInterface("IMailbox")
'    szBaseFolder = IMailbox.BaseFolder
'    if (0=len(szBaseFolder)) Then <TRAP ERROR HERE>

    ' Create a message object. Not a lot we can do if we can't - could put further
    ' checking maybe to write to event log. Exercise for the reader though.
    if (NO_ERROR = rc) Then
        set oMsg = CreateObject("CDO.Message")
        if (err.number) or (oMsg is nothing) Then rc = -1
    end if

    ' Configure the outbound email
    if (NO_ERROR = rc) Then

        With oMsg.Configuration
            '.Fields(cdoSaveSentItems) = False

            ' CDOEX Specific next line
            '.Fields("") = cdoSendUsingExchange
            ' SMTP Specific lines instead of above commented out line
            .Fields("") = 2 ' SMTP By Port
            .Fields("") = SMTP_SERVER
            .Fields("") = SMTP_PORT
            .Fields("") = SMTP_AUTH
            .Fields("") = SMTP_TIMEOUT
            ' If using BASIC, need to set cdo/configuration/sendusername and sendpassword accordingly
            ' Update fields from the configuration
        end With

        oMsg.To = DEFAULT_RECIP
        oMsg.From = szFrom
        oMsg.Subject = DEFAULT_SUBJECT

        oMsg.TextBody = "Backup Job has failed" & vbcrlf
        oMsg.TextBody = oMsg.TextBody & "Error: " & szError & vbcrlf & vbcrlf
        oMsg.TextBody = oMsg.TextBody & "Backup Job: " & szCommandLine

        ' CDOEX/SendUsingExchange Specific to set the message flag
        'oMsg.Fields("urn:schemas:httpmail:messageflag")= MESSAGE_FLAG

        ' SMTP Way of doing this
        oMsg.Fields("urn:schemas:mailheader:Reply-By") = szUTC
        oMsg.Fields("urn:schemas:mailheader:X-Message-Flag") = MESSAGE_FLAG

       oMsg.Fields(cdoImportance) = cdoHigh
    end if

    set oMsg = Nothing

    ' If using CDOEx
    'set oPerson = Nothing
    'set oADSInfo = Nothing

End Function

Someone smart may wonder what's oMsg.Fields("")=2 about. If you want to know, and how I went about finding out, it's a long story. Post a comment and I'll put up the details when I get a spare day (it's that long). <GRIPE>Previous to working at Microsoft, obtaining info like this was my day job - I had plenty of support cases lined up at MS and got pretty up tight about some of the decisions made by the Exchange Dev team, and specifically those working on the SDK tools. Seems like even since my gripes a few years ago, the same lack of documentation (or deliberate hiding of this info more specifically is still there)</GRIPE>

 (Nov 2008) Updated as I discovered the formatting of the above script was awful in the new blog template. Also adding a link to a direct download of the script (save as a .vbs file rather than .txt). It's here.

Comments (7)
  1. Carlos Arras says:

    I can’t get it to run. I get the e-mail with the red and the flag and all that, but it doens’t start any backups. I changed

    Const BACKUP_SHARE = "F:ExchangeBackups"

    Const BACKUP_SELECTION = "F:ExchangeBackupsMailbox Stores Normal Backups.bks"

  2. Hi Carlos

    Good that you’re getting an email at least. You havve checked and double-check that f:ExchangeBackups exists with permissions for you to write in that folder, and the "Mailbox Stores Normal Backups.bks" also exists in that folder?

    Debugging steps:

    – What’s the contents of the email sent to you? Does it give any clues as to the failure?

    – Immediately after the line…, add a new line "wscript.echo szCommandLine"

    – Run backup.vbs from the command line (ie start/run/cmd then cscript backup.vbs. Copy the text into a .cmd file (eg test.cmd), run test.cmd and see if the backup utility even opens.

    If you still don’t get anywhere, post back the output from the script with the new line in place and I’ll take a further look. Should be trivial to fix, I hope 😉



  3. dreas says:

    Better late than never… in regards to Carlos problem (which I think he solved himself already):

    I encountered the same problem and managed to get it working by changing the lines to…

    Const BACKUP_SHARE = "F:ExchangeBackups"

    Const BACKUP_SELECTION = "Exchange Backups.bks"

    …where Exchange Backups.bks is located at F:ExchangeBackups.

    Btw, thanks for the script, John, looks like it’s saving me a lot of time. 🙂

  4. Stefan Nielsen says:

    This really is a great script! Thank you very much…

  5. Joe says:

    Hey John… I can tell it’s been some time since someone responded but thanks a million. I’ve been reading this script looking for the logic since I’m not a programmer but it doesn’t matter. My networking job just got much easier using this script. Thanks once again…

  6. Clark says:


    Great Script! Can it send an email to say that it completed ok and not just on error? Thanks!

  7. cheap comptuers says:

    wow…Great work i would say it’s really saving time..i have been looking for such scrip. thank you very much.

Comments are closed.

Skip to main content