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 http://blogs.technet.com/jhoward' ************************************************************************************
Option ExplicitOn error resume nextConst NO_ERROR = 0Const BACKUP_PROGRAM = "c:\windows\system32\ntbackup.exe "Const cdoImportance = "urn:schemas:httpmail:importance"Const cdoHigh = 2 ' ImportanceConst cdoAnonymous = 0Const cdoBasic = 1Const cdoNTLM = 2Dim SMTP_AUTH
' TAILOR THESE NEXT CONSTANTS TO YOUR REQUIREMENTSConst DEFAULT_SENDER = "Backup Job <firstname.lastname@example.org>"Const DEFAULT_RECIP = "email@example.com"Const DEFAULT_SUBJECT = "Backup Failure on Exchange!"Const SMTP_SERVER = "exchange.contoso.com"Const SMTP_PORT = 25 SMTP_AUTH = cdoAnonymous ' Choose one of the aboveConst SMTP_TIMEOUT = 60 ' Seconds to wait for SMTP ServerConst MESSAGE_FLAG = "URGENT: Backup Job has failed"Const BACKUP_SHARE = "\\RemoteServer\ExchangeBackups"Const BACKUP_SELECTION = "Exchange Backup Selection.bks"
Dim szYYWW ' Date in YYYY-WW format (Week of year)Dim szYYMMDD ' Date in YYYY-MM-DD formatDim szFlagsSelection ' The backup selection script, prepopulatedDim szSetDescription ' The description of the backup setDim szDestinationFile ' The destination file in the destination directoryDim 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 NoneDim szFlagsAppend ' Flags for appending data [/a] or nothingDim 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 existsDim owShell ' To execute a shell commandDim rc ' Return codeDim szError ' If we have an error, record it in hereDim szCommandLine ' What we are going to run as a backupDim szUTC ' UTC Date/Time for SMTP Reply-By field
Set oFSO = Nothingset owShell = Nothingrc = NO_ERROR ' OK So farszCommandLine = "" ' Not sure what we're running yetszError = "" ' Not had an error yet
' Setup our variablesif (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 Objectif (NO_ERROR = rc) Then err.clear 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 ifend if
' Look to see if the file exists to determine the backup typeif (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 else ' 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 = Nothingend if
' Create a Shell Object to be able to run the backup executableif (NO_ERROR = rc) Then err.clear Set owShell = wscript.createobject("wscript.shell") if (err.number) or (owShell is nothing) Then rc = -2 szError = "Failed Creating wscript.shell: " & err.description & " -0x" & hex(err.number) end ifend if
' Build the backup command and run itif (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 & " "
set owShell = Nothingif (rc) Then SendErrorEmailwscript.quit(rc)
Function FormatNumber(szIn) FormatNumber = szIn if len(szIn) = 1 then FormatNumber = "0" & szInEnd 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_MACHINE\System\CurrentControlSet\Control\TimeZoneInformation\ActiveTimeBias" lMinutesOffset = 0 err.clear
if (NO_ERROR = rc) Then err.clear 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)) & " " & _ "-0000"
set oShell = Nothing err.clear ' Don't pass any error back as we will have _something_ GetUTCSMTPDateString = szUTC
'-----------------------------------------------------------------------------------' 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 = "firstname.lastname@example.org"'oMsg.From = "Exchange@contoso.com"'oMsg.Subject = "Backup Status"'oMsg.TextBody = "Whatever you want"'oMsg.Send'set oMsg = Nothing
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
' COMMENTED OUT - COULD USE IF WE KNOW THIS IS RUNNING ON AN EXCHANGE SERVER' 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 err.clear 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("http://schemas.microsoft.com/cdo/configuration/sendusing") = cdoSendUsingExchange ' SMTP Specific lines instead of above commented out line .Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2 ' SMTP By Port .Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTP_SERVER .Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = SMTP_PORT .Fields("http://schemas.microsoft.com/cdo/configuration/smtpauthenticate") = SMTP_AUTH .Fields("http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout") = SMTP_TIMEOUT ' If using BASIC, need to set cdo/configuration/sendusername and sendpassword accordingly ' Update fields from the configuration .Fields.Update 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("http://schemas.microsoft.com/mapi/proptag/0x10900003")=2 '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 oMsg.Fields.Update oMsg.Send end if
set oMsg = Nothing
' If using CDOEx 'set oPerson = Nothing 'set oADSInfo = Nothing
Someone smart may wonder what's oMsg.Fields("http://schemas.microsoft.com/mapi/proptag/0x10900003")=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.
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:\ExchangeBackups\Mailbox Stores Normal Backups.bks"
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?
- What's the contents of the email sent to you? Does it give any clues as to the failure?
- Immediately after the line rc=wscript.run..., 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 ;)
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. :)
This really is a great script! Thank you very much...
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...
Great Script! Can it send an email to say that it completed ok and not just on error? Thanks!
wow...Great work i would say it's really saving time..i have been looking for such scrip. thank you very much.