Tales from the Script - November 2003

By The Scripting Guys

If Scripting is Outlawed, only Outlaws will have Scripts

For a list and additional information on all Tales from the Script columns, click here.

SG090301

One of the criticisms leveled at those of us who work for Microsoft is the idea that Microsoft employees live in a fantasy world, that we only concern ourselves with idealized situations and are never confronted with the real problems faced by real system administrators. Now, that might be true of some Microsoft employees, but we Scripting Guys are bit miffed at the idea that we don’t know what it’s like to be a real system administrator. And others agree with us. For example, as we were driving to work this morning, we said to the chauffeur, “Bentley, do you believe we Scripting Guys live in a fantasy world and are totally out-of-touch with the needs of real system administrators?” “No, sir,” said Bentley. “I definitely do not.”

Well, at least that’s what we think he said. To tell you the truth, it was a bit hard to hear him over the sound of the Jacuzzi in the backseat.

The point is, we Scripting Guys are regular guys, no different than anyone else, and it hurts us to be called “elitist snobs” who wouldn’t know system administration if it bit us. Nevertheless, as we were putting the polo ponies out to pasture the other day, we wondered if perhaps we had been a bit remiss in one crucial area: maybe—just maybe, mind you—we haven’t done a very good job of helping system administrators when it comes to security issues.

Now, we admit that it might sound a bit odd for Scripting Guys to be talking about security; after all, a lot of people believe that if it wasn’t for scripting we wouldn’t have any security problems. With each new hacking or cracking incident and with each new virus or worm, people are quick to pin the blame on scripting, and, by extension, those of us who promote scripting. Maybe we shouldn’t even allow scripting, they say. Maybe Microsoft should remove Windows Script Host from the operating system, maybe macros should be permanently disabled. Maybe we ought to just toss the Scripting Guys in jail and throw away the key. (Interestingly enough, even people who aren’t in favor of banning scripting are still in favor of the latter idea.)

We’ll even admit something else: we can’t deny that scripting has been an accessory to several of these crimes. Yes, we’re aware of Kak.hta. Yes, we’re aware of the ILoveYou.vbs virus. (Hey, we were the first ones to fall for it. After all, we just assumed that everyone did love us.) But suppose we really did get rid of scripting? Would that finally bring an end to all this madness?

Well, it’s a nice thought, but winning $100 million in the Powerball lottery is a nice thought, too and the odds of either one actually happening are pretty much the same. (Which is to say, zero.) Sure, worms and viruses have been written as scripts, but worms and viruses have also been written as batch files. Are we going to ban all batch files as well? Worms and viruses have been written as executable files. Are we going to ban all executable files? Remove all the executable files from your computer, and you’ll be left with a machine that looks cool but can’t do anything interesting or useful.

Hey, we just invented the Macintosh! (Note to Macintosh fans everywhere: We’re just kidding.)

The truth is, scripts might make it easier to write a worm or a virus, although even that’s debatable (some of these viruses are pretty sophisticated). But if scripts were banned, then virus writers would simply go back to their roots and write viruses using C or C++. And remember, while worms and viruses get most of the headlines, even if we somehow eliminated all worms and all viruses that still wouldn’t mean computing would be 100 percent safe and secure. Suppose an Internet-connected user shares out his or her entire hard drive. In that case, you wouldn’t need a script to wreak havoc on that machine. Suppose a user configures his or her computer to automatically log on whenever someone—anyone—turns it on. Suppose someone gets tired of trying to remember his or her password, and so sets the local Administrator password to their user name or to the word password. Suppose—well, you get the idea.

The fact of the matter is this: as long as we allow people to use computers then there’s a chance that something bad will happen. But that’s no different than anything else in life. If we allow people to drive cars, there’s a chance they might run a red light or a chance they might back into the mailbox. There isn’t much we can do about that. All we can do is educate people about safe driving practices, and provide seat belts, airbags, and other protective devices that can help limit any damage they might cause.

And that happens to be the message behind this month’s column. Can we stop people from spreading viruses? Can we stop them from leaving their computers vulnerable to attack? Can we make computing 100 percent secure, eliminating the chance that anything could ever go wrong? No, we can’t, at least not without unplugging all network adapters, shutting off all Internet access, and tying all our users up in the closet. (Note to system administrators everywhere: We know that sounds tempting, but that is not an official Scripting Guy recommendation.) Nevertheless, there are some things that we can do to help reduce vulnerability and to help stop problems before they occur. And, crazy as this might sound, scripts can play a very important role in helping make our computers more secure. (Yes, we really did say more secure. Trust us.)

On This Page

Knowledge is Power
One Final Thought

Knowledge is Power

Ok, ok: when we look down the halls and see who has the power around here, we sometimes wonder what knowledge has to do with it, too. Be that as it may, it rarely hurts to have as much knowledge and as much information as you can get. And this is especially true when it comes to computer security. The more you know about your computers and how they are configured the better off you are. That’s why security experts recommend that you conduct periodic security audits on all your machines. That leaves just one question: how exactly do you go about conducting periodic security audits on all your machines?

As it turns out, Microsoft has released a pretty cool little tool—the Microsoft Baseline Security Analyzer—that can help you do these security audits. (Hopefully you’ve already downloaded the free Security Analyzer; if you haven’t, click here.) The Security Analyzer does just what the name suggests: it analyzes a computer (or, if you prefer, a whole slew of computers) and identifies potential security vulnerabilities, things like security patches that haven’t been installed or local accounts that are using a password of password (or maybe no password at all). It’s a great little tool, and no home or office should be without one.

Of course, by now you’re probably thinking, “Ok, this Security Analyzer sounds pretty cool, and I’ll definitely get my mom one for Christmas. But what does this have to do with scripting? If the Security Analyzer does everything I need it to, what would I even do with a script? Am I wrong about that, Scripting Guys?”

Actually, you’re right: If the Security Analyzer does everything you need it to do then you don’t need a script. (You probably don’t need that doughnut, either. Why don’t you mail it to the Scripting Guys, c/o Microsoft, and let us take care of it for you?) But what if the Security Analyzer doesn’t do everything you need it to do? After all, the Security Analyzer does a fantastic job of retrieving information, but it doesn’t ever act on that information. For example, the Security Analyzer could go out, check all 5,000 of your workstations, and report whether or not the Guest account has been disabled on each one. (Most organizations prefer to have the Guest account disabled, the better to prevent anonymous and non-authenticated users from gaining access to the network.) Let’s say 2,967 of your computers have the Guest account enabled. The Security Analyzer will inform you of this fact, but it will still be up to you to go out and disable the Guest account on each of those 2,967 computers.

And this is where scripting comes in. Suppose you want to know if the Guest account is disabled on a computer. You could write a script to do that, but if all you’re doing is checking the status of the account, well, what’s the point? After all, the Security Analyzer will make this check for you, and you won’t have to write a single line of code. However, what if you wanted to do more than just run an audit, what if you wanted to disable any enabled Guest accounts? You can’t do that with the Security Analyzer. But you can do that with a script.

And that’s the point we’re trying make here. (Those of you who have read the ADSI Scriptomatic Readme might find this hard to believe this, but once in awhile we Scripting Guys actually do have a point.) Scripting is a useful tool because it can help you retrieve security-related information, but it’s an indispensable tool because it can act on that information. And that’s what we’re about to demonstrate: we’re going to show you the information the Security Analyzer returns, we’re going to show you how to write a script that returns the same information and then, whenever possible, we’re going to show you how your script can be modified to act on that information.

Full disclosure. Ok, we fudged the truth just a little: We’re not going to replicate all the functionality of the Security Analyzer simply because we need to keep this column to a semi-reasonable length. Therefore, we’ll skip a few items, such as checking SQL Server account passwords. If there’s enough interest, we’ll go over these items in a future column. We hope that will be OK with you; Bentley said it was OK with him.

By the way, in future columns we’ll delve more deeply into security-related topics. For example, we’ll explain (or at least try to explain) the strange and mysterious world of security descriptors and how you can manage them using scripts. And we’ll show you how scripts can help you manage hot fixes and security patches. But we’ll worry about that stuff later. For now though, and without any further adieu, let’s start telling you what the Security Analyzer can do, and show you how you can do the same thing with a script.

Note: Well, ok, just a little bit more adieu. To keep our script snippets as short as possible, everything you see here is designed to run on one machine (and the local one at that). One of the strengths of the Security Analyzer is the fact that you can tell it to run against multiple computers. Can you do that with a script? You bet; for more details, see this previous Tales from the Script column. And keep your eyeballs peeled, and start watching for the Runomatic to be posted to the Script Center.

What’s the Runomatic? Ah, you wouldn’t want us to spoil the surprise, would you?

Task No. 1: Retrieve the Computer Name

To tell you the truth, there are about a million ways to retrieve the computer name from within a script. However, because WMI will be our primary technology for retrieving information, we’ll show you a WMI script that returns the name of the computer that the script is running against:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set colComputers = objWMIService.ExecQuery _
    ("Select * from Win32_ComputerSystem")
For Each objComputer in colComputers
    Wscript.Echo objComputer.Name
Next

This might seem like a silly thing to do, and it probably is if your script is running only against one computer. But if it’s running against 100 computers, it helps to know which computer is which.

Task No. 2: Retrieve the IP Address

Retrieving the IP address is also easy to do with a script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer & "\root\cimv2")
Set IPConfigSet = objWMIService.ExecQuery _
    ("Select IPAddress from Win32_NetworkAdapterConfiguration " _
        "Where IPEnabled=TRUE")
For Each IPConfig in IPConfigSet
    If Not IsNull(IPConfig.IPAddress) Then
        For Each strAddress in IPConfig.IPAddress
            WScript.Echo strAddress
        Next
    End If
Next

There are just two things to watch out for when trying to determine a computer’s IP address. First, WMI considers all sorts of things—including VPN and RAS connections—to be part of your network adapter configuration. Because of that, we use the Where clause Where IPEnabled = TRUE to limit the returned data to actual network adapters.

Second, IP addresses are always returned as an array; consequently, we need to use a For-Each loop to actually get the address. If we just tried to directly echo the value of the IPAddress property, we’d get a “Type mismatch” error.

Task No. 3: Report the Date and Time the Security Audit was Conducted

This is almost too easy, but as long as we’re replicating most of the functionality of the Security Analyzer, here’s a line of code that echoes the date and time:

Wscript.Echo Now

And some of you think scripting is hard!

Task No. 4: Check for Service Packs

Ok, we admit it: no one has ever released a perfect operating system. (Um, if anyone from Microsoft asks, we didn’t say that. Got it?) There are always bugs that are found after an operating system is released; sadly, there are also security holes and vulnerabilities that are found as well. No one likes the fact that these bugs are discovered, but that’s life. No one likes to cut their finger, either, but if you do you don’t just sit there and bleed to death, you haul out the first aid kit and put a bandage on it. And if an operating system needs a fix of some kind, well, then you haul out the first aid kit and put a service pack on it.

In other words, it’s really a good idea to make sure your computers have the latest service pack installed. The Security Analyzer can provide you with this kind of information; so can the following script:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
    Wscript.Echo objOperatingSystem.ServicePackMajorVersion  _
        & "." & objOperatingSystem.ServicePackMinorVersion
Next

Before you ask, here’s a point of clarification: WMI can’t tell you all the service packs that have been installed on a computer; it can only tell you the most recent service pack that was installed. That shouldn’t be a problem, however, because each new service pack contains all the fixes and updates included in previous service packs. If you install Service Pack 4 on Windows 2000, you not only get the new items found in Service Pack 4, but also everything that was included in Services Packs 1, 2, and 3. In other words, as long as you know Service Pack 4 was installed on a computer, you don’t have to worry about whether Service Packs 1, 2, or 3 were also installed. In effect, they were.

Task No. 5: Check for Hot Fixes

You know what was really tragic about some of the recent virus and worm attacks: the fact that a patch had been available for quite some time that, if applied, would have kept a computer safe from harm. For any number of reasons, though, people didn’t always apply the patch, and when the worm hit—well, you know the rest. The moral of the story? Keep up-to-date on the latest patches and hot fixes (for more information about how to do that, click here), and make sure your computers all have the requisite fixes installed.

So how can you tell which hot fixes have been installed on a computer? Funny you should ask:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colQuickFixes = objWMIService.ExecQuery _
    ("Select * from Win32_QuickFixEngineering")
For Each objQuickFix in colQuickFixes
    Wscript.Echo "Description: " & objQuickFix.Description
    Wscript.Echo "Hot Fix ID: " & objQuickFix.HotFixID
Next

Note: If you’re running Windows 2000, some of your computers might hang when trying to access the Win32_QuickEngineering class. For information about this known problem, as well as information about how to fix it, click here.

This script works just fine, but, to be perfectly honest, it’s nowhere near as slick as what the Security Analyzer does. That’s because the Security Analyzer does something really cool: it not only tells you what hot fixes are installed but also what hot fixes are not installed. In other words, if there’s a crucial hot fix that hasn’t been installed on a computer, it reports that.

How does the Security Analyzer know what’s crucial and what isn’t? Well, when you run the Analyzer, it references a file (Mssecure.xml) that contains a list of important hot fixes, as well as information about which computers (that is, which versions of Windows) require each hot fix. The Analyzer can then compare the list of critical hot fixes recorded in Mssecure.xlm with the hot fixes found on a computer, and identify any that are missing.

Implementing Mssecure.xml goes beyond what we can do in this one column (though it wouldn’t be impossible for someone with a knowledge of XML). However, in a future column we’ll show you how you can implement a similar idea, targeting specific hot fixes and patches.

Task No. 6: Check the Number of Local Administrators

Too many cooks spoil the broth, and too many local administrators—well, they’d likely spoil the broth, too. More importantly, though, having too many local administrators means you have innumerable chances for someone to make a mistake and leave a computer open to attack. After all, you might give a spare key to your next-door neighbors, the better for them to water the plants and feed the cat any time you’re away. However, there’s a reason why you don’t hand out spare keys to everyone you meet on the bus. (We’re assuming you don’t do that.) And there’s a reason why you want to limit the number of local administrators who, by definition, can do pretty much anything they want to a computer.

Just like the Security Analyzer, a script can return a list of all the members of the local Administrators group on a computer. In fact, here’s the code that does that very thing:

Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set objGroup = GetObject("WinNT://" & strComputer &
"/Administrators,group")
For Each objUser in objGroup.Members
    Wscript.Echo objUser.Name
Next

This is useful stuff; whenever you get the time (assuming that you can get the time), you can peruse the list and, if necessary, delete anyone that really shouldn’t be a local admin after all.

But what if your organization already has a policy regarding who can (and who cannot) be a local administrator? For example, suppose you’ve decided that no regular users can be admins; instead, the only acceptable administrators are the local Administrator user account and the domain Administrator account (we’ll assume for this example that you work for fabrikam.com). In that case, you don’t really want the script to tell you that it found several extraneous accounts in the Administrators group; instead, what you really want the script to do is delete those extraneous accounts, leaving only Administrator and fabrikam\Administrator. That would be cool, wouldn’t it?

Well, if that’s what you want, why didn’t you say so? Here’s a script that enumerates all the members of the Administrators group. If it finds an account other than Administrator or fabrikam\Administrator it uses the Remove method to remove that account from the Administrators group.

Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set objGroup = GetObject("WinNT://" & strComputer &
"/Administrators,group")
For Each objUser in objGroup.Members
    If objUser.Name <> "Administrator" and objUser.Name <> _
        "fabrikam\Administrator" Then
        objGroup.Remove(objUser.ADsPath)
    End If
Next

And, yes: this is a perfect example of how a script can be used to augment the capabilities of the Security Analyzer.

Task No. 7: Check for Non-Expiring Passwords

If there’s one thing that users hate, it’s passwords that expire; after all, no sooner do they memorize their password then they’re required to change it. So if users hate expiring passwords, why use them? (We mean besides revenge for all those cups of coffee that users have spilled on their keyboards and mice.)

Well, the truth is, hackers hate expiring passwords as much as users do. Suppose a hacker decides to try a dictionary attack. He or she might rule out the password aardvark, only to have the user change the password to aardvark midway through the attack. Even if a hacker manages to guess the password, the victory might be only temporary; the hacker might succeed in guessing the password only to have the user promptly change it. Changing passwords on a regular basis can help tighten security, and the best way to ensure that passwords get changed regularly is to make sure that they expire regularly.

Because passwords that don’t exoire represent a security vulnerability, the Security Analyzer will let you know if a machine has any local user accounts with non-expiring passwords. As you might have guessed, we just happened to have a script that does the same thing:

Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set colAccounts = GetObject("WinNT://" & strComputer & "")
colAccounts.Filter = Array("user")
For Each objUser In colAccounts
    Set usr = GetObject("WinNT://" & strComputer & "/" & _
        objUser.Name & ", user")
    flag = usr.Get("UserFlags")
    If flag AND ADS_UF_DONT_EXPIRE_PASSWD Then
        Wscript.Echo usr.Name & ": Password does not expire."
    Else
        Wscript.Echo usr.Name & ": Password expires."
    End If
Next

And if you’d just as soon reset all the passwords so that they do expire:

Const ADS_UF_DONT_EXPIRE_PASSWD = &H10000
Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set colAccounts = GetObject("WinNT://" & strComputer & "")
colAccounts.Filter = Array("user")
For Each objUser In colAccounts
    Set usr = GetObject("WinNT://" & strComputer & "/" & _
        objUser.Name & ", user")
    flag = usr.Get("UserFlags")
    If flag AND ADS_UF_DONT_EXPIRE_PASSWD Then
        usr.UserFlags = flag XOR ADS_UF_DONT_EXPIRE_PASSWD
        usr.SetInfo
    End If
Next

Task No. 8: Do a Local Account Password Test

Worms and viruses understandably get all the attention, but sometimes it’s the simplest things—like the humble Post-It note—that pose the real threat to computer security. In many offices, it’s not unusual to see people write their password on a Post-It Note and then tack that note to their monitor. Now, you could forbid this practice, and even punish anyone caught with an illicit pack of Post-It notes. However, that could lead to a different problem (we mean besides the problem of smugglers bringing in bootlegged Post-It Notes). Why do people write their passwords down? Because those passwords are hard to remember. If users can’t write those passwords down, then they might logically decide to use simple passwords—like password—that they can remember without the need for a Post-It.

Even an amateur hacker or malcontent can manage to break into a computer if it uses the password password. And while the Security Analyzer can’t yet detect the presence of Post-It notes on a monitor, it can check to see if any of the local user accounts:

  • Use a blank password

  • Use the same password as the user account name

  • Use the same password as the computer name

  • Use the word password as the password

  • Use the word admin or administrator as a password

How does the Security Analyzer pull off such a cool test? After all, there’s no way to retrieve a user’s password. That, too, would be a security breach. Well, to tell you the truth, we don’t know. However, we suspect it uses the ADSI ChangePassword method (or an equivalent API) to try and change the user’s password. Why ChangePassword? Because this method will fail unless you know the user’s current password; you have to supply both the current password and the new password. Because the Security Analyzer doesn’t actually change the password, we suspect it simply supplies the same password (for example, the word password) as both the current password and the new password, something like this:

objUser.ChangePassword "password", "password"

If the current password is not the word password, this method will fail and an error will occur. If the current password is the word password, the method will succeed, no error will be generated, and the password will be changed to, well, the word password. The net effect, of course, is that the password doesn’t get changed, but the script now knows that the current password is actually the word password. That might sound confusing, but take a careful look at the script and what it does. The idea is really pretty simple.

And, yes, this is the same kind of thing a hacker might try. We’re using hacker methodology for good rather than evil.

Here’s a script that checks to see if an account is using a password of password. This script can easily be modified to see if a script is using a blank password, using the logon name as a password, etc.

On Error Resume Next
Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
strPassword = "password"
Set colAccounts = GetObject("WinNT://" & strComputer & "")
colAccounts.Filter = Array("user")
For Each objUser In colAccounts
    objUser.ChangePassword strPassword, strPassword
    If Err = 0 or Err = -2147023569 Then
        Wscript.Echo objUser.Name & " is using the password " & _
            strpassword & "."
    End If
    Err.Clear
Next

Note: You might have noticed that our script checks to see if no error occurs (Err = 0); if there’s no error, that means that the password was changed, which means we know that the account has a password of password. However, we’re also checking to see if error -2147023569 occurs. This error is generated if the current password happens to be password, but the password can’t be changed because not enough time has elapsed since the last password change. (By default, passwords can only be changed once every 14 days.)

Task No. 9: Check the File System

If a computer is running Windows NT, Windows 2000, Windows XP, or Windows 2003, it’s important that each hard disk be formatted with the NTFS file system. (Ok, there might be some rare exceptions to that rule, but we’ll skip those for now.) Unless you like the idea of leaving your hard drives wide-open to the world, you need to format those drives with NTFS, and then take advantage of NTFS security. Again, it’s like home security: unless you want everyone and their dog to have free access to your home and everything in it, then you really should put locks on your doors. And, you really ought to lock those doors any time you’re gone.

So how do you check to see what file system has been installed on a hard disk? Here’s a very simple way.

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colDisks = objWMIService.ExecQuery _
    ("Select * from Win32_LogicalDisk Where DriveType = 3")
For Each objDisk in colDisks
    Wscript.Echo "Disk drive: "& objDisk.DeviceID & " -- " & objDisk.FileSystem
Next

In the preceding WQL query, you might have noticed that we requested only those logical disks where the DriveType was equal to 3. Why did we do that? Well, a DriveType of 3 represents a hard drive; by limiting the returned data to hard drives, we don’t muddy the waters by also getting back information about floppy drives, CD-ROM drives, or other entities that we already know won’t (and can’t) have the NTFS file system installed.

Task No. 10: Check for AutoLogon

It’s possible to configure Windows so that each time the computer starts it automatically logs you on using a name and password stored in the registry. That sounds pretty handy, and it does have some uses for home computers without a persistent Internet connection and for computers used in kiosks. On the other hand, it’s a bit of a security hole, to say the least; after all, that allows anyone who turns the computer on to log on as the user whose name and password are stored in the registry. Because of that one of the checks the Security Analyzer performs is to see whether or not AutoLogon has been enabled. Not to be outdone, you can make the same check using a script:

Const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Windows NT\CurrentVersion\WinLogon"
strValueName = "AutoAdminLogon"
objReg.GetDWORDValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName,
dwValue
If dwValue = 1 Then
    Wscript.Echo "Auto logon is enabled."
Else
    Wscript.Echo "Auto logon is disabled."
End If

Again, you typically don’t want Autologon enabled on a machine. So instead of merely informing you that AutoLogon is enabled, why not use a script to disable it? Something like this:

Const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Windows NT\CurrentVersion\WinLogon"
strValueName = "AutoAdminLogon"
dwValue = 0
oReg.SetDWORDValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName,
dwValue

Task No. 11: Check Status of the Guest Account

Depending on how you want to look at it, the Guest account simply provides a way for someone to gain access to your computer without having to identify themselves. (We suppose the only exception to that would be actor Christopher Guest.) Because of that, the Guest account is disabled by default in Windows XP and Windows 2003, and probably should be disabled in Windows 2000 and Windows NT 4.0 as well. Here’s a script that reports the status of the Guest account on the local computer:

Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set objUser = GetObject("WinNT://" & strComputer & "/Guest")
If objUser.AccountDisabled Then
    Wscript.Echo "The Guest account is disabled."
Else
    Wscript.Echo "The Guest account is enabled."
End If

Now, that’s cool, but suppose you’ve already decided that the Guest account must be disabled on all your computers. This modified script checks the status of the Guest account, and then does one of two things. If the Guest account is already disabled it reports that fact; if the Guest account is enabled, it goes ahead and disables it.

Set objNetwork = CreateObject("Wscript.Network")
strComputer = objNetwork.ComputerName
Set objUser = GetObject("WinNT://" & strComputer & "/Guest")
If objUser.AccountDisabled Then
    Wscript.Echo "The Guest account is already disabled."
Else
    objUser.AccountDisabled = True
    objUser.SetInfo
    Wscript.Echo "The Guest account has been disabled."
End If

Task No. 12: Check for Anonymous Access

By default, non-authenticated users can connect to any Windows computer and obtain a list of domain user names and share names. So what? Well, a hacker needs two pieces of information to log on as you: your user name and your password. Simply by plugging a laptop into your network and querying the nearest computer he or she can obtain your user name, thus halving the amount of information needed to hack your account. (Thank goodness no one has yet invented wireless, Internet-ready Post-It Notes, though we’re sure someone is working on that.)

So how do you know whether your computer is handing out information as though it was Halloween candy? Well, you can use the Security Analyzer, or you can use this script:

Const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "System\CurrentControlSet\Control\Lsa"
strValueName = "RestrictAnonymous"
objReg.GetDWORDValue HKEY_LOCAL_MACHINE,strKeyPath,strValueName,dwValue
If dwValue = 0 Then
    Wscript.Echo "Anonymous access is enabled."
Else
    Wscript.Echo "Anonymous access is disabled."
End If

Funny you should ask; there is a way for a script to turn off anonymous access on a computer:

Const HKEY_LOCAL_MACHINE = &H80000002
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "System\CurrentControlSet\Control\Lsa"
strValueName = "RestrictAnonymous"
dwValue = 1
oReg.SetDWORDValue HKEY_LOCAL_MACHINE, strKeyPath, strValueName,
dwValue

Task No. 13: List Installed Services

It’s always useful to know what services are running on a computer. After all, some viruses insidiously install themselves as services, and you obviously have to know if any of those babies are running on a computer. But it can also be useful to know if legitimate services are running on a computer. Yes, we know you told your users not to run Web servers or FTP servers on their computers, but how do you know they listened to you? Well, this script will tell you:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colRunningServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service")
For Each objService in colRunningServices
    Wscript.Echo objService.DisplayName, objService.State
Next

And what happens if you find out that someone is running a service you’d just as soon they not run? Well, here’s both a hint (just stop those services) as well as a script that stops the Universal Plug and Play Device Host service:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colRunningServices = objWMIService.ExecQuery _
    ("Select * from Win32_Service Where Name = 'upnhost'")
For Each objService in colRunningServices
    objService.StopService()
Next

Task No. 14: Check Shares and Share Permissions

Wouldn’t you know it: all your life you’ve been taught how important it is to share, and now we find out that sharing—at least the computer kind—can actually be a bad thing.

The ability to share files and folders is definitely a mixed blessing. File sharing allows users to collaborate on projects without the need for administrator help or file server storage space; unfortunately, it also allows them to share files (such bootlegged MP3 files) that they shouldn’t be sharing in the first place. In addition, a network share provides people with easy access to a computer. If you share a folder without correctly setting permissions on that folder, well ….

So can a script tell you if there are any shared folders on a computer? You bet:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colShares = objWMIService.ExecQuery("Select * from Win32_Share")
For each objShare in colShares
    Wscript.Echo "Name: " & objShare.Name
    Wscript.Echo "Path: " & objShare.Path
    Wscript.Echo "Type: " & objShare.Type
Next

Of course, a lot of organizations have decided it’s better just to eliminate shares altogether, at least on client computers. If that’s a decision you’ve made, then you’ll find scripts to be especially useful. The Security Analyzer can tell you whether any shared folders were found on a computer; however, it’s still up to you to connect to that computer and disable each and every share. A script, on the other hand, can not only identify shared folders but can disable those shares as well. For example, here’s a script that disables all the shares with a Type of 0 (we’ll explain that in a moment):

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colShares = objWMIService.ExecQuery _
    ("Select * from Win32_Share Where Type = 0")
For each objShare in colShares
    objShare.Delete
Next

Note: Don’t panic; although the method is called Delete, it only deletes the share (that is, it stops sharing the folder over the network). It doesn’t actually delete the folder or anything that might be found in the folder. Although we suppose that deleting them would be one way to keep files from being shared across the network.

So why Type 0? In the world of WMI, a share with a Type of 0 represents a regular old shared folder. There are, of course, other kinds of shares, most notably Administrative shares (like C$). The preceding script gets rid of shared folders while leaving other shares (such as Administrative shares) alone. If you want to disable all shared resources, just leave the Where clause out of your query:

Set colShares = objWMIService.ExecQuery("Select * from Win32_Share")

Note: What about share permissions? As we noted earlier, we’ll talk about security descriptors in a future column.

Task No. 15: Check Windows Version

What does it mean if you have Service Pack 1 installed? Well, if you’re running Windows XP it means you’re up-to-date; if you’re running Windows 2000, however, it means you’re behind the times (Windows 2000 is now up to Service Pack 4). That means that some of the information returned by these scripts is meaningless unless you know the version of Windows installed on the computer. Here’s a script that will tell you that:

strComputer = "."
Set objWMIService = GetObject("winmgmts:\\" & strComputer &
"\root\cimv2")
Set colOperatingSystems = objWMIService.ExecQuery _
    ("Select * from Win32_OperatingSystem")
For Each objOperatingSystem in colOperatingSystems
    Wscript.Echo objOperatingSystem.Caption, objOperatingSystem.Version
Next

Task No. 16: Check Office Macro Security

Microsoft Office lets each user decide how to handle macros that might be embedded in a document. You can elect to:

  • Only run macros from trusted sources (that is, macros signed with a trusted certificate). This is high security.

  • Allow the user to individually decide which macros to run and which macros not to run. This is medium security.

  • Allow all macros to run. This is low security.

By setting macro security to high, you can —among other things—prevent autorun macros (macros that automatically run any time you open a document or start a program) from doing nasty things to your computer. Needless to say, it might be a good idea to initially configure your Office installations with tight security; it might be an even better idea to periodically check to make sure that tight security is still in force.

Checking the macro security level can be a bit tricky; that’s because the path to the registry value you need to change can vary depending on what version of Office you have installed. We don’t have room to talk about figuring out the version of Office (hint: use the WMI class Win32_Product) and then determining the appropriate registry path, but here, at least, is a script that retrieves the macro security level for Outlook 2000. You should be able to easily modify this script to fit your needs and your version of Office.

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Office\10.0\Outlook\Security"
strValueName = "Level"
objReg.GetDWORDValue HKEY_CURRENT_USER, strKeyPath, strValueName,
dwValue
If dwValue = 3 Then
    Wscript.Echo "Outlook macro security is set to high."
ElseIf dwValue = 2 Then
    Wscript.Echo "Outlook macro security is set to medium."
Else
    Wscript.Echo "Outlook macro security is set to low."
End If

The preceding script checks the macro security level for Outlook. What if you want to check the macro security level for Word, Excel, PowerPoint, or Access? Well, in that case, just change the registry path as needed:

strKeyPath = "Software\Microsoft\Office\10.0\Word\Security"
strKeyPath = "Software\Microsoft\Office\10.0\Excel\Security"
strKeyPath = "Software\Microsoft\Office\10.0\PowerPoint\Security"
strKeyPath = "Software\Microsoft\Office\10.0\Access\Security"

Task No. 17: Check IE Security Zones

To help protect users against malicious Web sites, Internet Explorer uses security zones to govern the activities (run scripts, install ActiveX controls, etc.) that can and cannot be done any time you visit a Web site. By default, these security zones provide differing levels of security: you can do things in the Local Intranet Zone (like run scripts) that you can’t do in the Restricted Sites Zone.

If you’d like to know more about security zones, we recommend you check out the Internet Explorer Enhanced Security Configuration white paper (the paper was written for Windows 2003, but the information about security zones holds true for other versions of Windows as well). In the meantime, here’s a script that will report the security level for the Local Intranet Zone (Zone 1):

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Windows\CurrentVersion\Internet
Settings\Zones\1"
strValueName = "CurrentLevel"
objReg.GetDWORDValue HKEY_CURRENT_USER,strKeyPath,strValueName,dwValue
Select Case dwValue
    Case 73728
        Wscript.Echo "The security zone is set to high security."
    Case 69632
        Wscript.Echo "The security zone is set to medium security."
    Case 66816
        Wscript.Echo "The security zone is set to medium-low security."
    Case 65536
        Wscript.Echo "The security zone is set to low security."
    Case Else
        Wscript.Echo "The security zone is set to custom security."
End Select

You can also retrieve security information for Trusted Sites (Zone 2), Internet (Zone 3) and Restricted Sites (Zone 4). Just change the registry path accordingly:

Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2
Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\3
Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\4

Most of the time, this approach gives you what you need to know. The only time you’ll have a problem is if a user has modified one or more of the settings in a security zone. In that case, the security level will be reported as Custom, but you’ll have no way of knowing whether this new, custom level has tighter security than the default level or looser security than the default level. Here, again, is an area where scripts shine: you can use a script to determine (and/or configure) each individual security setting in each security zone. For more information, see the Internet Explorer Enhanced Security Configuration white paper, or keep your eyeballs peeled for the Tweakomatic. (No, that’s not a misprint. We really are going to release something called the Tweakomatic. Hey, you had to figure we’d do something like that sooner or later.)

Task No. 18: Check Outlook Security Zone

Microsoft Outlook also uses Internet Explorer’s security zones to determine whether scripts and active content can be run from messages formatted as HTML. To prevent scripts embedded in an HTML message from running on a computer you need to do two things:

  • Make sure the ability to run scripts is disabled in an Internet Explorer security zone (typically the Restricted Sites zone).

  • Make sure Outlook has been configured to use that security zone.

The Outlook security zone is recorded in the registry; again, the exact path to that registry value depends on which version of Office you have on a computer. If you have Office 2000 installed, you can use this script to determine the Outlook security zone:

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Office\10.0\Outlook\Options\General"
strValueName = "Security Zone"
objReg.GetDWORDValue HKEY_CURRENT_USER,strKeyPath,strValueName,dwValue
Select Case dwValue
    Case 4
        Wscript.Echo _
            "Outlook is using settings from the Restricted Sites zone."
    Case 3
        Wscript.Echo "Outlook is using settings from the Internet
zone."
    Case 2
        Wscript.Echo "Outlook is using settings from the Trusted Sites
zone."
    Case 1
        Wscript.Echo "Outlook is using settings from the Local Intranet
zone."
    Case Else
        Wscript.Echo "The Outlook security zone could not be
determined."
End Select

On most computers, the Restricted Sites zone has the tightest security restrictions. Because of that, you might want to go ahead and configure Outlook settings for all your users, ensuring that Outlook uses Restricted Sites for its HTML security. Here’s a script that does just that:

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg=GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Software\Microsoft\Office\10.0\Outlook\Options\General"
strValueName = "Security Zone"
dwValue = 4
objReg.SetDWORDValue HKEY_CURRENT_USER, strKeyPath, strValueName, dwValue

One Final Thought

Again, we want to emphasize one point: if the Security Analyzer fits your needs, then there’s no reason for you to write a script that does the exact same thing the Security Analyzer does. (We Scripting Guys are firm believers in not doing anything that you absolutely don’t have to do.) On the other hand, the one limitation of the Security Analyzer is that it does only what it’s programmed to do. That’s a limitation simply because there might be other security checks that you feel are important to make. For example, maybe you want to make sure that all your users are using password-protected screensavers. Can the Security Analyzer tell you that? No. However, you could easily write a script to do that:

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg =GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Control Panel\Desktop"
ValueName = "ScreenSaverIsSecure"
objReg.GetStringValue HKEY_CURRENT_USER, strKeyPath, ValueName,
strValue
If strValue = "1" Then
    Wscript.Echo "The screen saver is password protected."
Else
    Wscript.Echo "The screen saver is not password protected."
End If

And then, of course, you could use a script to go ahead and password protect the screen saver:

Const HKEY_CURRENT_USER = &H80000001
strComputer = "."
Set objReg =GetObject("winmgmts:\\" & strComputer &
"\root\default:StdRegProv")
strKeyPath = "Control Panel\Desktop"
ValueName = "ScreenSaverIsSecure"
strValue = "1"
objReg.SetStringValue HKEY_CURRENT_USER, strKeyPath, ValueName,
strValue

So maybe you use the Security Analyzer for some tasks and a script for others. The point is, you shouldn’t think about security as an either-or proposition: either you use the Security Analyzer or you write a script. Instead, use whatever makes the most sense. Scripting, after all, can often best be used by complementing existing tools, not by replacing them. Just do whatever works best for you.

Or, at the very least, just do whatever you can talk your butler into doing.

Have questions or comments about this column? Write to scripter@microsoft.com (in English, if possible). Ask for Bentley.