Thursday, March 23, 2006
#
I went into a new customer recently and found that they had about a gazillion standalone servers, all of which had the same administrator password, and they used the Administrator account to run some services.
While there oftem seem to be good reasons for doing this (pass through authentication, only requiring a single account having elevated privileges, etc.) it almost always turns out to be a bad idea. It makes it difficult to change passwords when employees turn over, and you can have service failures when you change passwords.
And, lest you forget, there are other things that could possibly depend on these passwords too: COM objects and websites, to mention two other possibilities.
So, I wrote a script that, based upon a simple input file containing a list of computers, goes out and checks for all services running on each computer that are using a non-default account. For the purposes of my script, I considered these accounts to be default accounts: "NT AUTHORITY\LocalService", "LocalSystem", ".\ASPNET", "NT AUTHORITY\NETWORK SERVICE", "NT AUTHORITY\NetworkService". In your environment, those will probably work, but I make no guarantees for that. :-)
This script also specifically identifies the total number of services running under the administrator account. Those are things you really probably want to change!
The input file is easy to generate from adfind/dsquery/dns - whatever way you can use to produce a list of servers in your environment, one server per line. If you need to have comment lines, then begin a line with the crosshatch character (”#”).
Sample summary output:
Processing complete
Total computers processed: 33
Total Administrator services: 27
Total special services: 62
Total errors: 0
Total comment lines: 0
Of course, as always, this script is provided “AS IS” and conveys no warranties.
Option Explicit
' check-services.vbs
Const ForReading = 1
Dim objFS, objFileIn, iCount, iError, iComment, iAdminCount, iTot
Dim strRemoteComputer
Sub e (str)
WScript.Echo str
End Sub
Function ErrorReport (str)
If Err.Number Then
iError = iError + 1
ErrorReport = True
e "Error 0x" & CStr (Hex (Err.Number)) & " occurred " & str
If Err.Description <> "" Then
e "Error description: " & Err.Description & "."
End If
Err.Clear
Else
ErrorReport = False
End If
End Function
Sub Startup
If Wscript.Arguments.Count <> 1 Then
e "Usage: Check-Services.vbs listofcomputers.txt"
e " "
e "'listofcomputers.txt' contains a list of the computers"
e "that will be checked for services that do not contain"
e "default accounts."
e " "
wscript.quit 1
End If
On Error Resume Next
Set objFS = CreateObject ("Scripting.FileSystemObject")
If ErrorReport ("while creating Scripting.FileSystemObject") Then
wscript.quit 1
End If
Set objFileIn = objFS.OpenTextFile (wscript.arguments (0), ForReading)
If ErrorReport ("while opening " & wscript.arguments (0)) Then
Set objFS = Nothing
wscript.quit 1
End If
iTot = 0
iCount = 0
iError = 0
iComment = 0
iAdminCount = 0
End Sub
Sub Shutdown
objFileIn.Close
Set objFileIn = Nothing
Set objFS = Nothing
End Sub
' Constants we need for WBEM calls
Const wbemFlagReturnImmediately = &H10
Const wbemFlagForwardOnly = &H20
Dim arrExclude
arrExclude = Array ("NT AUTHORITY\LocalService", _
"LocalSystem", _
".\ASPNET", _
"NT AUTHORITY\NETWORK SERVICE", _
"NT AUTHORITY\NetworkService")
Function CheckExclusions (ByVal strVal)
Dim i
For i = LBound (arrExclude) To UBound (arrExclude)
If LCase (strVal) = LCase (arrExclude (i)) Then
CheckExclusions = True
Exit Function
End If
Next
CheckExclusions = False
End Function
Sub CheckServicesOnComputer (ByVal strComputer)
Dim objWMIService
Dim colItems, objItem
Dim iExcluded, iIncluded
iExcluded = 0
iIncluded = 0
WScript.Echo ""
WScript.Echo "Checking computer " & strComputer
On Error Resume Next
Set objWMIService = GetObject ("winmgmts:\\" & strComputer & "\root\CIMV2")
If ErrorReport ("opening CIMv2") Then Exit Sub
Set colItems = objWMIService.ExecQuery ("SELECT name,startname,caption FROM Win32_Service", "WQL", _
wbemFlagReturnImmediately + wbemFlagForwardOnly)
If ErrorReport ("on ExecQuery") Then Exit Sub
On Error Goto 0
' if we get any items returned, it's installed
For Each objItem In colItems
If CheckExclusions (objItem.StartName) Then
iExcluded = iExcluded + 1
Else
iIncluded = iIncluded + 1
If Right (LCase (objItem.StartName), Len ("\Administrator")) = "\administrator" Then iAdminCount = iAdminCount + 1
'wscript.echo "name = " & objItem.Name & " startname = " & objItem.StartName & " caption = " & objItem.Caption
e " startname = " & objItem.StartName & " caption = " & objItem.Caption
End If
Next
Set colItems = Nothing
Set objWMIService = Nothing
Wscript.echo "Included items = " & iIncluded & " excluded items = " & iExcluded & " for computer " & strComputer
iTot = iTot + iIncluded
End Sub
' Main
Call Startup
Do Until objFileIn.AtEndOfStream
strRemoteComputer = objFileIn.ReadLine
If ErrorReport ("while reading input line") Then
Exit Do
End If
' If the first character is a "#", then the line is a comment
If Left (strRemoteComputer, 1) <> "#" Then
Call CheckServicesOnComputer (strRemoteComputer)
iCount = iCount + 1
Else
iComment = iComment + 1
End If
Loop
Call Shutdown
e " "
e "Processing complete"
e "Total computers processed: " & iCount
e "Total Administrator services: " & iAdminCount
e "Total special services: " & iTot
e "Total errors: " & iError
e "Total comment lines: " & iComment
Tuesday, March 07, 2006
#
Unfortunately, due to the volume of blog spam I've been receiving, I've had to disable comments. When I can upgrade to a more recent version of Community Server, I'll reenable them at that time.
Wednesday, December 28, 2005
#
I've been promising an update to my Exchange Catchall script, since almost the week it was first released.
The number one request has always been for it to handle more domains (the original version only handled two, and changing it to handle more was beyond the scripting skills of most folks).
The second most common request was to allow multiple copies of the script to run (usually because of the number one request). While that can be done by using multiple SMTP virtual servers, I don't recommend it. With this update, it shouldn't ever be necessary.
Philosophically, the days of catchall mailboxes are probably numbered. Many messaging experts recommend against them. Once a domain is targeted, a directory harvesting attack can cause catchall mailboxes to get slammed with thousands of messages in a very short period of time. Many mail systems now return an error, or drop the SMTP connection, or refuse connections from SMTP servers that send email to invalid addresses. (Exchange could be fairly easily extended to do those things, but it would require a protocol sink, not an event sink.)
However, many folks still want a catchall mailbox. Most mail servers other than Exchange support them in the base product. This is a nice simple implementation of one and this version supports an arbitrary number of domains.
The original version was tested on both Exchange 2000 Server and Exchange Server 2003. I no longer have an Exchange 2000 Server to test on, but none of the interface layer changed in this update, so the new version should still work on both versions of Exchange.
You can download the Exchange 200x Catchall Script, Version 2, here.
I am not aware of any other free tool that performs this function for multiple domains (Mailbasket Lite from http://turbogeeks.com will do it for a single domain).
Enjoy!
Wednesday, December 21, 2005
#
A number of folks have been running into Outlook 2003 POP3 problems, where either Outlook hangs or generates a timeout.
It is pretty obvious that there was an undocumented change in Exchange 2003 service pack 2 about the handling of POP3 messages. To read the relevant KB articles, the issues have been there for quite a while, but SP2 seems to exacerbate them...or it's all a coincidence. :-)
First of all, please ensure that you have Outlook 2003 service pack 2 installed. That fixes some of the recent issues.
Second, if you are using S/POP (POP3 with SSL), try KB 827349. Even though this KB only refers to Outlook 2002, it also applies to Outlook 2003.
Finally, try the registry change described in KB 816896. Note that this change has to be made on your Exchange server and requires, at a minimum, a POP3SVC restart.
Wednesday, November 16, 2005
#
While attempting to figure out the "right" way to take Apache SSL keys and convert them for use in IIS, I learned more than I ever wanted to know about SSL certificates.
Here is the benefit of that process for you as well as some great debugging tools.
First, you need openssl installed on your desktop. I got it here (a precompiled binary distribution):
Find a list of available distributions here:
Second, you need a command that takes the key/crt files and converts them to pkcs12 (which is the basis for the PFX format that Windows uses). Here is an example:
C:\OpenSSL\bin>openssl pkcs12 -export -out alta.p12 -inkey "y:\sslcerts\2005-alta-key.txt" -in "y:\sslcerts\2005-alta.txt"
The "-export" is very important. Windows will not use an SSL key that has not been marked exportable. If you get the certificate as attachments from an Apache installation, the private key will have a ".key" extension and the certificate itself will have a ".crt" extension. You must have both a certificate and a private key. (You can do a similar thing if you are provided the combined key and cert in DER or PEM/X.509 format -- it's covered in the openssl help file if you need that information.)
Third, you need to import the key into the Windows certificate store:
a) Start -> Run -> mmc
b) File -> Add/Remove Snap-In...
c) Click "Add" on the "Add/Remove Snap-In" dialog
d) Double-click "Certificates" on the "Add Standalone Snap-In" dialog
e) Select "Computer Account" and then click "Next"
f) Select "Local Computer" and then click "Finish"
g) Click "Close" on the "Add Standalone Snap-In" dialog
h) Click "OK" on the "Add/Remove Snap-In" dialog
i) Expand "Certificates (Local Computer)" in the main MMC window
j) Expand "Personal" and then select "Certificates" beneath the "Personal" node
k) Right-click on the selected "Certificates" node and then select All Tasks -> Import
l) Click "Next" on the Certificate Import Wizard
m) Browse to locate the file created by openssl ("alta.p12" in the above example) and then click "Next"
n) Enter a password for the certificate and check the box for "Mark this key as exportable" and then click "Next"
o) Verify that "Place all certificates in the following store" and "Personal" are selected and then click "Next"
p) Click "Finish"
q) Close the MMC
Fourth, you need to assign the certificate to a website.
Note: you typically need to have selected a new IP address and assigned it to the webserver prior to this step
a) Start -> Administrative Tools -> IIS Manager
b) Expand "<server-name> (local computer)" and then expand "Web Sites"
c) Right-click on the relevant website and select "Properties"
d) Click on the "Directory Security" tab
e) Click the "Server Certificate" button
f) Click "Next" on the Web Server Certificate button
g) Select "Assign an Existing Certificate" and then click "Next"
h) Select the certificate you loaded in the prior step and then click "Next"
i) Click "Next" (443 should be pre-filled, and you do not want to change this value)
j) Verify the information on the "Certificate Summary" window and then click "Next"
k) Click "Finish"
l) Close IIS Manager
Complete?
Now, you should be done. Note, however:
1) A mismatched private key and certificate will still allow you to generate the pkcs12 file. However, once you've loaded the certificate into Windows, and view the certificate, you will receive one of two indications that a problem exists:
a) It will say "a mismatch exists between the certificate and private key in this certificate", or
b) It will not say anything, where it should say: "You have a private key that corresponds to this certificate"
2) It is possible to get Windows to generate pretty decent debugging information, but it takes a reboot. See KB 260729:
The value you want to use for the EventLogging registry value is 7 to get maximum information. After you are done, you really want to set it back to 1, as it slows down SSL processing significantly to do this logging. And this does work for Windows Server 2003, even thought that isn't mentioned.
3) A pretty easy way to check whether a cert has been loaded properly and generated properly is to get "checkcert" from Steve Johnson's blog:
4) Microsoft has a diagnostics tool named SSL Diag that generates a temporary self-signed certificate to help see if the certificate is the problem, or if it is something else. It also has a fair bit of just "interesting" information.
Enjoy your SSL debugging!
M
Wednesday, November 02, 2005
#
I've shown a number of routines for dealing with hexadecimal numbers and byte arrays. One that I haven't yet shown is taking an escaped hex string and converting it to a regular hex string.
An escaped hex string is kind of a bastardized format - it uses regular characters when they can easily be represented in the local character set (like using '0' for 0x30 instead of \30). The escaping allows you to represent “special” characters. You see this quite a bit when dealing with GUIDs and with non-English names.
For a list of characters that must be escaped when constructing LDAP queries, see ADSI:Search Filter Syntax in MSDN.
The format of an escaped hex string is a backslash character “\” followed by the two hexadecimal digits for the character. Or a single character which is converted to its hex value when the string is converted (or not - it depends on the use of the string).
Here is a small program that shows you how to convert them:
' example escaped hex string (this is a GUID)
Const EscapedString = "\AB\95s\F5\F2\02DK\A2\CF\E5\07\A9\96q\EA"
wscript.echo "Escaped string: " & EscapedString
ResultString = ConvertEscapedToHex (EscapedString)
wscript.echo "Result string: " & ResultString
Wscript.echo "Length = " & Len (Resultstring) & " chars"
wscript.quit 0
Function ConvertEscapedToHex (str)
'
' take an escaped hex string and turn it into a fully hex string
'
Dim i, j, newstr
newstr = ""
While Len (str) > 0
If Left (str, 1) = "\" Then
newstr = newstr & Mid (str, 2, 2)
str = Right (str, Len (str) - 3)
Else
i = Asc (str)
newstr = newstr & Right ("0" & Hex (i), 2)
str = Right (str, Len (str) - 1)
End If
Wend
ConvertEscapedToHex = newstr
End Function
Thursday, September 15, 2005
#
I don't know that I would ever do this, but I thought the solution was pretty cool.
If you have a situation where you want to prevent users from using Deleted Item Recovery in Outlook, you have a mechanism for preventing them from doing so. However, a savvy user can still go to Outlook Web Access. But it takes a VERY smart user to figure out how to do Deleted Item Recovery from OWA for anything but the default Deleted Items folders.
But you, the administrator, can still do Deleted Item Recovery for the user. As noted, doing this makes more work for you - the Exchange administrator. But in a control-heavy environment it might be worth it.
This information came from: Nikki Peterson - EGOVX [NikkiPeterson@mail.maricopa.gov].
How to disable an Outlook user’s ability to "Recover Deleted Items":
- Exit and Logoff from Outlook
- Delete the file [c:]\Program Files\Microsoft Office\Office\Addins\dumpster.ecf
- Restart Outlook
NOTE: If you receive an error on startup, go to the ADD-INS section of outlook, and remove the check mark from the Recover Deleted Items option.
How to undo or re-enable an Outlook user’s ability to "Recover Deleted Items":
- Exit and Logoff from Outlook
- Copy the file dumpster.ecf to [c:]\Program Files\Microsoft Office\Office\Addins
- From ADD-INS, select INSTALL, and select dumpster.ecf
Tuesday, September 13, 2005
#
This has come up a number of times, and I actually thought I'd blogged about it in the past -- but I guess not.
If you have users that only use POP and/or IMAP, and never log into via Outlook/Exchange or OWA Premium, then those users do not get notified when their password is about to expire.
You can write a script that sends your users e-mail when their passwords are about to expire. The script below is based on a Scripting Clinic article available here, with some bug fixes and enhancements.
This script, with a few changes and enhancements, is also in my upcoming book from O'Reilly: “Essential Exchange Server 2003“.
'
' exch-pwd-expires.vbs
'
' Michael B. Smith
' March 21, 2005
'
' This program scans all users in the Users container and all organizational units
' beneath the HOSTING_OU organizational unit, for users whose passwords have either
' already expired or will expire within DAYS_FOR_EMAIL days.
'
' An email is sent, using CDO, via the SMTP server specified as SMTP_SERVER to the
' user to tell them to change their password. You should change strFrom to match
' the email address of the administrator responsible for password changes.
'
' You will, at a minimum, need to change the SMTP_SERVER, the HOSTING_OU, and the
' STRFROM constants. If you run this on an Exchange server, then SMTP_SERVER can
' be "127.0.0.1" - and it may be either an ip address or a resolvable name.
'
' If you don't have an OU containing sub-OU's to scan, then set HOSTING_OU to the
' empty string ("").
'
Option Explicit
' Per environment constants - you should change these!
Const HOSTING_OU = "Hosting"
Const SMTP_SERVER = "127.0.0.1"
Const STRFROM = "emailadmin@your.domain"
Const DAYS_FOR_EMAIL = 15
' System Constants - do not change
Const ONE_HUNDRED_NANOSECOND = .000000100 ' .000000100 is equal to 10^-7
Const SECONDS_IN_DAY = 86400
Const ADS_UF_DONT_EXPIRE_PASSWD = &h10000
Const E_ADS_PROPERTY_NOT_FOUND = &h8000500D
' Change to "True" for extensive debugging output
Const bDebug = False
Dim objRoot
Dim numDays, iResult
Dim strDomainDN
Dim objContainer, objSub
Set objRoot = GetObject ("LDAP://RootDSE")
strDomainDN = objRoot.Get ("defaultNamingContext")
Set objRoot = Nothing
numdays = GetMaximumPasswordAge (strDomainDN)
dp "Maximum Password Age: " & numDays
If numDays > 0 Then
Set objContainer = GetObject ("LDAP://CN=Users," & strDomainDN)
Call ProcessFolder (objContainer, numDays)
Set objContainer = Nothing
If Len (HOSTING_OU) > 0 Then
Set objContainer = GetObject ("LDAP://OU=" & HOSTING_OU & "," & strDomainDN)
For each objSub in objContainer
Call ProcessFolder (objSub, numDays)
Next
Set objContainer = Nothing
End If
'========================================
' Add the number of days to the last time
' the password was set.
'========================================
'whenPasswordExpires = DateAdd ("d", numDays, oUser.PasswordLastChanged)
'WScript.Echo "Password Last Changed: " & oUser.PasswordLastChanged
'WScript.Echo "Password Expires On: " & whenPasswordExpires
End If
WScript.Echo "Done"
Function GetMaximumPasswordAge (ByVal strDomainDN)
Dim objDomain, objMaxPwdAge
Dim dblMaxPwdNano, dblMaxPwdSecs, dblMaxPwdDays
Set objDomain = GetObject("LDAP://" & strDomainDN)
Set objMaxPWdAge = objDomain.maxPwdAge
If objMaxPwdAge.LowPart = 0 And objMaxPwdAge.Highpart = 0 Then
' Maximum password age is set to 0 in the domain
' Therefore, passwords do not expire
GetMaximumPasswordAge = 0
Else
dblMaxPwdNano = Abs (objMaxPwdAge.HighPart * 2^32 + objMaxPwdAge.LowPart)
dblMaxPwdSecs = dblMaxPwdNano * ONE_HUNDRED_NANOSECOND
dblMaxPwdDays = Int (dblMaxPwdSecs / SECONDS_IN_DAY)
GetMaximumPasswordAge = dblMaxPwdDays
End If
End Function
Function UserIsExpired (objUser, iMaxAge, iDaysForEmail, iRes)
Dim intUserAccountControl, dtmValue, intTimeInterval
Dim strName
On Error Resume Next
Err.Clear
strName = Mid (objUser.Name, 4)
intUserAccountControl = objUser.Get ("userAccountControl")
If intUserAccountControl And ADS_UF_DONT_EXPIRE_PASSWD Then
dp "The password for " & strName & " does not expire."
UserIsExpired = False
Else
iRes = 0
dtmValue = objUser.PasswordLastChanged
If Err.Number = E_ADS_PROPERTY_NOT_FOUND Then
UserIsExpired = True
dp "The password for " & strName & " has never been set."
Else
intTimeInterval = Int (Now - dtmValue)
dp "The password for " & strName & " was last set on " & _
DateValue(dtmValue) & " at " & TimeValue(dtmValue) & _
" (" & intTimeInterval & " days ago)"
If intTimeInterval >= iMaxAge Then
dp "The password for " & strName & " has expired."
UserIsExpired = True
Else
iRes = Int ((dtmValue + iMaxAge) - Now)
dp "The password for " & strName & " will expire on " & _
DateValue(dtmValue + iMaxAge) & " (" & _
iRes & " days from today)."
If iRes <= iDaysForEmail Then
dp strName & " needs an email for password change"
UserIsExpired = True
Else
dp strName & " does not need an email for password change"
UserIsExpired = False
End If
End If
End If
End If
End Function
Sub ProcessFolder (objContainer, iMaxPwdAge)
Dim objUser, iResult
objContainer.Filter = Array ("User")
Wscript.Echo "Checking company = " & Mid (objContainer.Name, 4)
For each objUser in objContainer
If Right (objUser.Name, 1) <> "$" Then
If IsEmpty (objUser.Mail) or IsNull (objUser.Mail) Then
dp Mid (objUser.Name, 4) & " has no mailbox"
Else
If UserIsExpired (objUser, iMaxPwdAge, DAYS_FOR_EMAIL, iResult) Then
wscript.Echo "...sending an email for " & objUser.Mail
Call SendEmail (objUser, iResult)
Else
dp "...don't send an email"
End If
End If
End If
Next
End Sub
Sub SendEmail (objUser, iResult)
Dim objMail
Set objMail = CreateObject ("CDO.Message")
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserver") = SMTP_SERVER
objMail.Configuration.Fields.Item ("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
objMail.Configuration.Fields.Update
objMail.From = STRFROM
objMail.To = objUser.Mail
objMail.Subject = "Password needs to be set for " & Mid (objUser.Name, 4)
objMail.Textbody = "The active directory password for user " & objUser.userPrincipalName & _
" (" & objUser.sAMAccountName & ")" & vbCRLF & _
"will expire in " & iResult & " days. " & vbCRLF & _
"Please change it as soon as possible." & vbCRLF & vbCRLF & _
"Thank you," & vbCRLF & _
"Your email administrator"
objMail.Send
Set objMail = Nothing
End Sub
Sub dp (str)
If bDebug Then
WScript.Echo str
End If
End Sub
Thursday, September 01, 2005
#
I had an interesting question asked of me today, and I had to spend some time looking into it, so I thought I'd share the result of that with you.
As you know, adprep is used for preparing Active Directory to include Windows Server 2003 domain controllers. It comes in two flavors:
adprep /forestprep
Which is, of course, run once per forest, and
adprep /domainprep
Which is, of course, run once per domain.
Forestprep
The forestprep process creates a new container named:
CN=Operations,CN=ForestUpdates,CN=Configuration,DC=ForestRootDomain
Each operation executed by adprep/forestprep causes a new GUID-named container object to be created in the above container.
If you are upgrading from any version of Windows 2000 Server to Windows Server 2003, and adprep/forestprep has completed successfully, then the CN=Operations container will have 36 of the GUID-name objects contained within it. If you are upgrading from a beta or release-candidate version of Windows Server 2003 to the released version, you may have as many as 43 of these objects.
If there are fewer than 36 objects, then adprep/forestprep failed somewhere along the process. You can compare the list of operations and GUIDs in the Microsoft KB article 309628.
Domainprep
Similar to forestprep, the domainprep operation also creates a new container:
CN=Operations,CN=DomainUpdates,CN=System,DC=DomainName
but unlike forestprep, domainprep also creates a second container:
CN=Windows2003Update,CN=DomainUpdates,CN=System,DC=DomainName
which contains a single attribute of interest CN=Revision.
The CN=Operations container will contain 50 updates (up to 55 if you are upgrading from beta-releases).
If all 50 of the operations are completed successfully, then the CN=Windows2003Update container will have its Revision attribute set to 8 (eight).
If the CN=Revision attribute is not eight, then each GUID in the CN=Operations,CN=DomainUpdates container may be examined to determine where adprep/domainprep failed. The list of these is also given in Microsoft KB article 309628.
Fun Fact
All of the non-schema updates that happen as a result of running adprep are executed on the domain controller which holds the infrastructure master FSMO role.
Thursday, August 25, 2005
#
In Exchange Server 2003, Microsoft fixed the disaster that was the infamous “M:” drive, and hid it again. But the functionality was not removed -- lots of stuff depends on it.
ExIFS provides many capabilities, one of them is that the Exchange stores can be treated like a NTFS file system. You can open emails, scan mailboxes, etc. Do pretty much whatever you want to do -- except NEVER EVER EVER change a permission using the BackOfficeStorage/ExIFS interfaces (or Windows Explorer or any similar tool). Exchange and NTFS use the same bits in access control lists, but they mean different things. Just say no.
Here is an example piece of code that shows you how to treat the Exchange stores. This does assume that you've got full permission to examine your stores. I cover this material in Chapter 12 of my upcoming book.
'
Option Explicit
'
Dim objFSO, objFolder, objF
Dim bFirstEmail, iEmailCount
'
bFirstEmail = True
iEmailCount = 0
'
Set objFSO = CreateObject ("Scripting.FileSystemObject")
Set objFolder = objFSO.GetFolder ("\\.\BackOfficeStorage")
'
For Each objF in objFolder.SubFolders
'If bDebug Then wscript.echo "Checking folder " & objF.Path
Call DoDirectory (objF, "")
Next
'
Set objFolder = Nothing
Set objFSO = Nothing
'
Sub DoDirectory (objFold, strIndent)
Dim objUserDir, objFile
'
WScript.Echo strIndent & "Checking Folder: " & objFold.Path
For Each objUserDir in objFold.SubFolders
Call DoDirectory (objUserDir, strIndent & vbTab)
Next
'
For Each objFile in objFold.Files
Wscript.echo strIndent & "Checking file: " & objFile.Path
If bFirstEmail Then
Dim objEmail, strLine
'
Set objEmail = objFSO.OpenTextFile (objFile.Path, 1)
Do Until objEmail.AtEndOfStream
strLine = objEmail.ReadLine
WScript.Echo strline
Loop
objEmail.Close
Set objEmail = Nothing
bFirstEmail = False
End If
'
iEmailCount = iEmailCount + 1
If iEmailCount > 10 Then
WScript.Echo "Done!"
WScript.Quit 0
End If
Next
End Sub