Saturday, July 30, 2011

Shadow Groups in Active Directory

Shadow Groups (SG) is an interesting topic. It’s not an actual type of group in the sense of security, distribution, global, domain local or universal, but rather a concept. A shadow group is a global security group which reflects the users found in a specific organizational unit (OU) in its group membership.

Shadow groups can be used for Fine Grained Password Policies, which can be applied to users (or inetOrgPerson objects) and global security groups only. Or if every user within a specific OU will require the same access to a resource, you can use shadow groups.

This means that you always want to keep the shadow groups up to date. If a new user is moved to the OU, it must also be added to the group. Likewise if a user is removed from the OU, it must also be removed from the group. There is no difference between a regular group and a shadow group in the sense that shadow groups bring any new or extra functionality when it comes to administration. These are just regular groups, but with a very specific purpose.

Keeping them up to date manually is really not an option, unless perhaps you know every single employee in the company personally and you are the only one managing AD. People aren’t machines, they make mistakes, it’s only human. Sooner or later, someone will forget to update the group when a user is moved between OUs.

This is when automation comes in handy. Anything that does not require human input should be automated.

Here we have a very simple criteria:
Users found in a specific OU should always be a member of a specific security group.
This does not require any human input or modification. We just need a way to compare the users in the OU with the users in the group and make any necessary changes.

Trust me when I say that there are many ways to do this. More than I will show you. I will limit this to the ds-tools and the PowerShell AD-cmdlets.


The Quick and Dirty version:
dsquery user “<Organizational Unit distinguishedName>” –scope onelevel | dsmod group “<Shadow Group distinguishedName>” –chmbr

This will look for all users found in the specified OU, and limit the search to that OU only. Then it will clear the current group membership of the SG and add all users currently found in the OU.

The Clean and Clever batch file version:
Set OU=Organizational Unit distinguishedName (without quotes)
Set Group=Shadow Group distinguishedName (without quotes)

dsget group %Group% –members | find /v /i “%OU%” | dsmod group “%Group%” –rmmbr
dsquery * “%OU%” –filter “(&(sAMAccountType=805306368)(!memberOf=%Group%))” –scope onelevel | dsmod “%Group%” –addmbr

This will look at the group membership, pipe it to the find command, to find only the users where the OU’s distinguishedName is NOT present, and then pipe it to dsmod group to remove those users from the group. The next step is to look for all users in the specified OU that are NOT member of the Shadow Group already. It will then add any users found to the group.


Windows Server 2008 R2 with Active Directory cmdlets:
$OU=”Organizational Unit distinguishedName”
$Group=”Shadow Group distinguishedName”

Get-ADGroupMember –Identity $Group | Where-Object {$_.distinguishedName –NotMatch $OU} | ForEach-Object {Remove-ADPrincipalGroupMembership –Identity $_ –MemberOf $Group –Confirm:$false}
Get-ADUser –SearchBase $OU –SearchScope OneLevel –LDAPFilter “(!memberOf=$Group)” | ForEach-Object {Add-ADPrincipalGroupMembership –Identity $_ –MemberOf $Group}

This will do the same thing as the ds-tools clean and clever version, except it’s done in PowerShell with the AD cmdlets.

Once you’ve decided for what approach you want to take, you can easily create a scheduled task for this and ensure that the batch or PowerShell script runs at intervals that suits your organization. Just make sure that the user account the scheduled task runs under has got the proper privileges (such as log on as batch job and permission to update the Shadow Groups (write members) in Active Directory).

Thursday, July 28, 2011

The last VBScript you will ever need?

One of the first things people notice when writing their very first PowerShell scripts is that clicking the script file won’t run it, but instead open up Notepad. Which is nice, if you want to open up Notepad to read the script.
But I’m guessing that’s not why you clicked it.

There are of course several different ways to launch a PowerShell script. One is to run powershell.exe and specify the script as one of the arguments. This is useful if you want to run the script from a batch file, a shortcut, a scheduled task or wherever else you can specify a program to run.

I figured it would be neat to make the PowerShell script “clickable” by creating a shortcut to it and tell it to be run by powershell.exe. But doing this by hand is tedious work! (And as we all know, tedious work is the very reason you started scripting in the first place.)

Enter: VBScript

It’s easy to create a shortcut using VBScript, and it’s piece of cake to drag and drop a file onto another. –Which is something VBScript gladly accepts, taking the dropped file’s path as an argument to pass to the script. Knowing this, we can put two and two together, and write a script which allows you to drop a .ps1 file onto it, and then have it create a shortcut for us!

Now, this is a very straight forward script, which takes one file and creates a shortcut to it, adding the necessary parameters. It can be modified to allow you to drop several files onto it, or to run powershell.exe with additional parameters (such as setting execution policy).

Option Explicit

If WScript.Arguments.Count = 0 Then
    WScript.Echo "Drag and drop a .ps1 file onto the script."
End If

Dim strPowerShellScriptFullPath : strPowerShellScriptFullPath = WScript.Arguments.Item(0)
Dim objFSO : Set objFSO = CreateObject("Scripting.FileSystemObject")

If Not objFSO.FileExists(strPowerShellScriptFullPath) Then
End If
If Not LCase(objFSO.GetExtensionName(strPowerShellScriptFullPath)) = "ps1" Then
End If

Dim strPowerShellScriptPath : strPowerShellScriptPath = Left(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim strPowerShellScript : strPowerShellScript = Mid(strPowerShellScriptFullPath, InStrRev(strPowerShellScriptFullPath, "\"))
Dim objShell : Set objShell = CreateObject("WScript.Shell")
Dim objShortCut : Set objShortCut = objShell.CreateShortcut(strPowerShellScriptPath & strPowerShellScript & " - Shortcut.lnk")

objShortCut.TargetPath = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe"
objShortCut.Arguments = "-NoExit -File """ & strPowerShellScriptFullPath & """"
objShortCut.WorkingDirectory = "%HOMEDRIVE%%HOMEPATH%"
objShortCut.IconLocation = "%SystemRoot%\System32\WindowsPowerShell\v1.0\powershell.exe, 0"
objShortCut.Description = "Run PowerShell script"

I named my script “Create PowerShell Shortcut.vbs”.
And as always, there’s a very real risk of lines wrapping where they shouldn’t. If anything other than a .ps1 file is dropped onto it, nothing will happen (not even a message, in the above example).

When a ps1 file is dropped onto it, the script will create a shortcut to powershell.exe and assign the ps1 file path as an argument. The shortcut will be created in the same location as the ps1 file was dragged from. The VBScript and ps1 file does not have to be in the same location.

(It can of course also be run from the command line, as long as you pass a proper path, but that sort of defeats the purpose of it, now doesn’t it..)

Sunday, May 22, 2011

PowerShell, Active Directory and Expiring Passwords

Have you ever wanted to find out how many days are left until your (or someone else’s) Active Directory user account password expires? This (and just about everything else) can be done using Windows PowerShell.

Windows Server 2008 introduced a new Active Directory attribute called “msDS-UserPasswordExpiryTimeComputed”. This is a constructed attribute, which keeps track of when the password expires. Before Fine Grained Password Policies (FGPP) it used to be a simple matter of comparing the user’s pwdLastSet attribute with today’s date and subtracting it from the domain’s pwdMaxAge attribute.

I say used to be simple, because now you have to take FGPP into account. But as it turns out, it’s actually easier now, thanks to the new attribute.

Here comes the command. It looks a bit daunting at first, but broken down it’s actually quite simple.

PowerShell with the AD cmdlets:
(([datetime]::FromFileTime((Get-ADUser -Identity AHultgren -Properties "msDS-UserPasswordExpiryTimeComputed")."msDS-UserPasswordExpiryTimeComputed"))-(Get-Date)).Days

What we do here is begin with the Get-ADUser cmdlet followed by the user name, in this case AHultgren: Get-ADUser –Identity AHultgren
We also need to get the property containing the password expiration date. We do that by using the –Properties parameter and specify "msDS-UserPasswordExpiryTimeComputed".

Now if we wrap that entire command within parenthesis, we can return just the value of the attribute we need:
(Get-ADUser –Identity AHultgren –Properties "msDS-UserPasswordExpiryTimeComputed").”msDS-UserPasswordExpiryTimeComputed”

In my case the number 129538573151710728 is returned. This is not very helpful, so we need to translate the number to an actual date. We can do this by pre-fixing the command with [datetime]::FromFileTime() to convert it to a proper date:
[datetime]::FromFileTime((Get-ADUser -Identity AHultgren -Properties "msDS-UserPasswordExpiryTimeComputed")."msDS-UserPasswordExpiryTimeComputed")

This returns Wednesday, June 29, 2011 3:41:55 PM.

We could end it all here, since we now know the date the password will expire. But it can be interesting to know how many days are left. We do that by simply subtracting today’s date:
(([datetime]::FromFileTime((Get-ADUser –Identity AHultgren -Properties "msDS-UserPasswordExpiryTimeComputed")."msDS-UserPasswordExpiryTimeComputed"))-(Get-Date)).Days

Both the AD cmdlet and Get-Date must each be enclosed within parenthesis, and both commands must in turn also be enclosed within parenthesis, and then we can use “.Days” to just return the number of days. Instead of “.Days” you can pipe it all to Get-Member to show the available methods and properties you can use.

Running the final command looks like this:
PS C:\Windows\system32> (([datetime]::FromFileTime((Get-ADUser –Identity AHultgren -Properties "msDS-UserPasswordExpiryTimeComputed")."msDS-UserPasswordExpiryTimeComputed"))-(Get-Date)).Days

This means my password will expire in 38 days.

As an added bonus, this is how you turn a date into an Integer8 value:
Which returns 129505176000000000.

Sunday, April 3, 2011

VBScript - WMI Ping

Sometimes it can come in handy to actually check, instead of just assume, that a destination is available before you let your script loose. One method you can use for this is ping. I recently wrote a log on script which pings a server before it attempts to map a network drive.

That script used a very simple, but yet effective, function that returned true or false based on the result. If ping failed (the function returned false), it would continue to try for 90 seconds before giving up. Here’s that little piece of code:

Function Ping(address)
    Ping = False
    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set objPing = objWMI.Get("Win32_PingStatus.Address='" & address & "'")
    If objPing.StatusCode = 0 Then
        Ping = True
    End If
End Function

WScript.Echo Ping("")

To test this function, save it in a text file with the file extension .vbs, and run the file. It should return either –1 (true) or 0 (false) to the screen.

But perhaps you want a bit more information than that, since the above script was not made for human readability, but rather to be used by other functions within the script. 

So I put together two more functions for this purpose. PingStatusCode and PingStatus. The former will get the return code and the latter will translate the code to text, when it is passed to it.

Function PingStatusCode(address)
    Set objWMI = GetObject("winmgmts:\\.\root\cimv2")
    Set objPing = objWMI.Get("Win32_PingStatus.Address='" & address & "'")
    PingStatusCode = objPing.StatusCode
End Function

Function PingStatus(PingStatusCode)
    Select Case Int(PingStatusCode)
        Case Int(0)
            PingStatus = "Success"
        Case Int(11001)
            PingStatus = "Buffer Too Small"
        Case Int(11002)
            PingStatus = "Destination Net Unreachable"
        Case Int(11003)
            PingStatus = "Destination Host Unreachable"
        Case Int(11004)
            PingStatus = "Destination Protocol Unreachable"
        Case Int(11005)
            PingStatus = "Destination Port Unreachable"
        Case Int(11006)
            PingStatus = "No Resources"
        Case Int(11007)
            PingStatus = "Bad Option"
        Case Int(11008)
            PingStatus = "Hardware Error"
        Case Int(11009)
            PingStatus = "Packet Too Big"
        Case Int(11010)
            PingStatus = "Request Timed Out"
        Case Int(11011)
            PingStatus = "Bad Request"
        Case Int(11012)
            PingStatus = "Bad Route"
        Case Int(11013)
            PingStatus = "TimeToLive Expired Transit"
        Case Int(11014)
            PingStatus = "TimeToLive Expired Reassembly"
        Case Int(11015)
            PingStatus = "Parameter Problem"
        Case Int(11016)
            PingStatus = "Source Quench"
        Case Int(11017)
            PingStatus = "Option Too Big"
        Case Int(11018)
            PingStatus = "Bad Destination"
        Case Int(11032)
            PingStatus = "Negotiating IPSEC"
        Case Int(11050)
            PingStatus = "General Failure"
        Case Else
            PingStatus = "Unknown Error"
    End Select
End Function

You can test the functions by adding these lines of code, outside the functions, before you run the script:

WScript.Echo PingStatusCode("") & vbTab & PingStatus(PingStatusCode(""))
WScript.Echo PingStatusCode("") & vbTab & PingStatus(PingStatusCode(""))

The script will first ping and give you the return code and message, then it will ping and, again, give you the return code and message.


Here’s the MSDN reference for the Win32_PingStatus Class:

Wednesday, March 23, 2011

PowerShell - Enable Exchange 2010 Mailbox from Active Directory

I installed Windows Live Writer the other day, so I figured I’d finally give it a shot. Step one was to load up with four cheese sandwiches, a cup of tea and a script! Earlier today when I pondered what to write about, I was deciding between managing NTFS inheritance with PowerShell or using redircmp to better control new domain computers.

Turns out I won’t be writing about either. In this post.

Lets instead turn our focus to Active Directory (AD) and Exchange 2010. Anyone who has worked with AD and Exchange before 2007 will most likely be familiar with the additional tabs in the Active Directory Users and Computers properties sheet for managing Exchange settings. These are gone in Exchange 2010, and as far as I know, won’t be coming back. All the Exchange management is now done in the Exchange Management Console (EMC). While EMC does allow you to create AD objects and mail/mailbox enable them, I find it a but cumbersome if all you want to do is create a mailbox for a user account.

One really nice touch with EMC is that it provides you with the PowerShell (PS) command to do <whatever it is you’re configuring>. Mailbox enabling a user account for example. This means if you’ve done it once in EMC you can easily do it in PowerShell instead. If you don’t mind typing or firing up a PS script to do this, it’s all good. But that’s not why we’re here.

Lets think back to the Exchange 2003 days and how we used to mailbox enable users by right clicking and going to “Exchange Tasks” and then selected what we wanted to do, and went through the wizard.

This is more like it!

Is there something similar in Exchange 2010? No. Not as far as I know (feel free to correct me). But if there’s a will, there’s a way! Since we already have the PowerShell command to mailbox enable the user, we can take advantage of the AD display specifiers and add our own menu item that will appear when you right click a user account. All it will take is a little bit of VBScript magic and an update of a configuration attribute in Active Directory.

We’ll start out by writing a VBScript. The script will perform a couple of checks to make sure that the user account is enabled (it’s a requirement for the PowerShell command), and that it isn’t already mail or mailbox enabled. Then it will proceed by launching PowerShell and finally execute the “Enable-Mailbox” cmdlet.

When a context menu item is selected, the object (ADsPath) itself is passed as an argument to the program.


Option Explicit
Dim objShell, objArgs, objUser

Set objShell = WScript.CreateObject("WScript.Shell")
Set objArgs = WScript.Arguments
Set objUser = GetObject(objArgs(0))

If Not IsEmpty(objUser.legacyExchangeDN) Then
    MsgBox "Object " & objUser.CN & " is already mail or mailbox enabled.", vbInformation, "Active Directory Domain Services"
End If

If objUser.AccountDisabled = True Then
    MsgBox "Object " & objUser.CN & " is disabled. Please enable before proceeding.", vbInformation, "Active Directory Domain Services"
End If

objShell.Run("powershell.exe -noexit Add-PSSnapin Microsoft.Exchange.Management.PowerShell.E2010; Enable-Mailbox -Identity '" & objUser.distinguishedName & "' -Alias '" & objUser.sAMAccountName & "'")

Set objArgs = Nothing
Set objUser = Nothing
Set objShell = Nothing

As always, watch out for unintentional line breaks.

Save this script and place it in a suitable location. I put mine in the \\testlab.local\NETLOGON share for high availability.

The next step is to edit the user account display specifier in the Active Directory configuration partition. I used ADSIEdit.msc to do this. Open it up and connect to the configuration naming context. The attribute is found in this location:

At the top you will find an attribute called “adminContextMenu”. We have to edit this attribute to add our entry to the context menu. Leave any entry already there, and add a new one. Mine looks like this:
2,Enable Mailbox,\\testlab.local\NETLOGON\Enable-Mailbox.vbs

The first position is simply where in the menu it should appear. I only had one entry, so I picked number 2. The second position is the name you want to appear in the context menu. Prefixing a letter with the ampersand character (&) adds a shortcut to it. The third entry is the location of the script. In this case, Enable-Mailbox.vbs in the netlogon share.

This MSDN article talks a bit more about the adminContextMenu:

Once applied, start or restart the Active Directory Users and Computers mmc and look up a user without a mailbox. Right click the user and now “Enable Mailbox” should appear. If you click it, PowerShell will start and load “Microsoft.Exchange.Management.PowerShell.E2010” (this must be present for this to work), and then run the cmdlet to enable a mailbox for the user account!

This should work if the EMC is installed on the system. If you’re trying to do this remote, you may have to use a remote pssession to first connect to a suitable server. I haven’t tried this yet, since right now there are no clients in my lab. –Maybe I’ll revisit this later.


As always, try this out in a lab first to ensure it behaves as expected, before you go live.

Thursday, March 17, 2011

Domain Computers - Local Administrators

With the addition of Group Policy Preferences (GPP) adding a domain group to the local Administrators group on client computers has become a real walk in the park. 

Here's how you do it, step by step. 

If you don't have a default computer policy already, I recommend you create one. Mine is called "Default Computer Policy". This policy contains the settings that all domain computers will have in common. And while you're at it, also create the security group you want to add, if you haven't already. Something short and simple like "Computer Administrators" will do just fine, and the name is self explanatory. 

Edit the GPO and expand the Computer Configuration, Preferences, Control Panel Settings and finally right click "Local Users and Groups" and select New -> Local Group. Select Update as action, then click the group name drop down arrow. Scroll down and select "Administrators (built-in)". Skip "Rename to" and "Description" and the checkboxes for deleting users and groups (unless you really want to restrict the membership to what you specify). In the "Members" box, click "Add..." and enter the name of the domain group you want to add to the local Administrators group. I'd suggest clicking the button with the three dots to search AD for the group. Make sure the action "Add to this group" is selected and click OK. Believe it or not, but click "Apply" and you're done. 

Now it's time to confirm the setting. You can either fire up a test machine in scope for this policy, or if it's already running, reboot it (it's a computer setting after all). If you're feeling lazy, you can even do this remotely. 

You can use psexec to connect to the computer (psexec \\computername cmd) and force a gpupdate (gpupdate /force). You can reboot the computer using shutdown /m "computername" /f /r /t 0. The switches are force, reboot and time in seconds. Once it's back up, you can once again connect to it using psexec, and the run the command "net localgroup administrators". This lists the members of the local Administrators group. 

For me, it worked like a charm! 

And here's how you did it back in the day with the "Restricted Groups" GPO setting. 

Create or edit your Default Computer Policy. Expand the Computer Configuration, Policies, Windows Settings, Security Settings and right click "Restricted Groups". Select "Add Group" and browse AD for the domain group you want to add to the local Administrators group. Click OK. 
In the bottom section, "This group is a member of", click "Add..." and browse for "Administrators". Click OK and then apply the setting. The setting should now say that group name "Domain\Group" (your domain\your group name) is a member of "Administrators". If it says anything else it has been configured wrong, and this may cause damage if rolled out. 

Enter: Copy and paste: 

Now it's time to confirm the setting. You can either fire up a test machine in scope for this policy, or if it's already running, reboot it (it's a computer setting after all). If you're feeling lazy, you can even do this remotely. 

You can use psexec to connect to the computer (psexec \\computername cmd) and force a gpupdate (gpupdate /force). You can reboot the computer using shutdown /m "computername" /f /r /t 0. The switches are force, reboot and time in seconds. Once it's back up, you can once again connect to it using psexec, and the run the command "net localgroup administrators". This lists the members of the local Administrators group. 

For me, it worked like a charm! 

Monday, March 14, 2011

VBScript - Desktop Shortcut

A quick one today, if you ever have the need to create a desktop shortcut using VBScript. This can be deployed in a user logon script (GPO or AD) for example.

Set objShell = CreateObject("WScript.Shell")
strDesktop = objShell.SpecialFolders("Desktop")
Set objLink = objShell.CreateShortcut(strDesktop & "\Andreas - Talk nerdy to me.lnk")
objLink.TargetPath = "C:\Program Files\Internet Explorer\iexplore.exe"
objLink.Arguments = ""
objLink.WorkingDirectory = "%HOMEDRIVE%%HOMEPATH%"
objLink.IconLocation = "C:\Program Files\Internet Explorer\iexplore.exe, 2"
objLink.Description = "Andreas - Talk nerdy to me!"

This link fires up Internet Explorer and goes straight to my blog. It should be fairly obvious 
what the different methods do and how you change them. 

Sunday, March 13, 2011

Domain Computers - Scheduled Reboot

Ever been assigned the task to set up nightly reboots of all your domain computers? I haven't. 
I have however been asked how to do this. Now, I'm pretty sure you can deploy this using for example SCCM, if it's available to you, but lets assume it isn't. So we'll go about this another way.

What about a GPO setting? Well, as far as I know (feel free to correct me) there isn't a GPO setting to configure a scheduled reboot (recurring or not).

You can use shutdown.exe /m to reboot a remote computer, but that's really for a one time thing, on one or a couple of computers. Running shutdown /m on all domain computers every night is not something I'd look forward to at least!

But what if we could make each computer run shutdown.exe on its own? I think we may be on to something!

So, lets write a batch file, shall we? How about we want all computers rebooted at 4 in the morning?

This is what the batch file would look like:
at 04:00 shutdown.exe /f /r /t 60

When this runs it will schedule the command "shutdown.exe /f /r /t 60" to be run at 04:00. It will also force (/f) any open programs to close, then reboot (/r) the computer after 60 seconds (/t 60). If anyone is using their computer at 4 AM, they'll have 60 seconds to save their work before it reboots itself. Once the task has been run, it's automatically removed. 

The next step is to create a new GPO and assign this batch file as a startup script. The reason I say create a new GPO is because you don't want to include something like a mandatory reboot in your default computer policy. 
Why, you ask? 
Well, image that you do configure this in your standard policy and everything is working just fine, until you receive that e-mail from the boss saying x, y and z computers have a business need to not be included in the nightly reboot. Now what? They're supposed to have the same settings as all other computers, except for the mandatory reboot. You've effectively painted yourself into a corner. 

This is why you create a new GPO and a security group. Name the GPO something along the lines of "Computer Nightly Reboot", and the group "Excluded from Nightly Reboots". Assign the batch file as a computer startup script, and deny the security group the right to apply the policy settings. This way, if any computer is going to be excluded from the reboots, just add them to the security group. Problem solved! 

A one time option to prevent a computer from rebooting is to log on to the computer (or use psexec) and run "at <task id> /delete", which will remove the task from the list. If the reboot has already been triggered, you can use the command "shutdown -a" to abort it. 

This post only explains how to do this using a batch file and a GPO computer startup script, but if Group Policy Preferences (GPP) are available to you, it's also possible to use them to configure a scheduled task for the computer. If you want to schedule a weekly reboot instead of a nightly, GPP is probably the best way to go. Although you can also use startup scripts, and perhaps a more advanced VBScript solution to either create a scheduled task, or to first check what day of week it is and if it's, say friday, schedule the reboot. 

Wednesday, March 9, 2011

DFS - What's that namespace?

I have on several occasions made an (albeit half-hearted) attempt to figure out how to check what DFS mode (2000 or 2008) a domain namespace (DFSN) is using. Last week I finally decided to get to the bottom of it.

If your domain functional level (DFL) is lower than 2008, then this should not be an issue. Windows Server 2000 mode is the only option.

However, if your DFL is Windows Server 2008, then you have an option to create a namespace using either Windows Server 2000 or 2008 mode. You can see which mode a namespace is using if you open the DFS Management console. Right click a namespace, click properties, and the mode is listed on the "General" tab. It can also be seen in the description bar if a namespace is selected.

But this is not what I wanted. I wanted a way to retreive this information using the command line.

The command line tools available for DFS management are dfscmd, dfsutil and dfsdiag. I checked the documentation and help/syntax for a way to display the DFSN mode. I found nothing.

I searched the Internet and posted in a forum on technet, but it bore no fruit.

I fired up ldp.exe and adsiedit to look for any clues, in case the information was stored there (the mmc console obviously knew which mode a namespace was in!).

The DFS configuration can be found here: 

Here I found the namespace attribute msDFS-SchemaMajorVersion. I found it on my 2008 namespaces but not on my 2000. I looked it up.

The article explains that this attribute was "Implemented on Windows Server® 2008 operating system and Windows Server® 2008 R2 operating system." Which means it's not present on Windows Server 2000 namespaces!

So, what can I do with this information? Can I check if the attribute is present on the namespace and make an assumption that it is either 2000 or 2008, depending on if it's there or not? Yep!

Now to the fun part. 

This VBScript will bind to the DFS-Configuration and loops through all the namespaces it contains. It will attempt to get and check for the msDFS-SchemaMajorVersion for each namespace to see if its empty or not. If it cannot retreive it, it will make the assumption that it's dealing with a namespace in WS 2000 mode. On the other hand, if it can retreive it, we're dealing with a 2008 namespace. Right? Well, I and my script will assume so. At least until the next Windows Server operating system is released. (Then perhaps we can check for the version number instead?).

If InStr(1, WScript.FullName, "wscript.exe", vbTextCompare) Then
  Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run "cmd /k cscript.exe """ & WScript.ScriptFullName & ""
End If

Set objRootDSE = GetObject("LDAP://RootDSE")
Set objSysInfo = CreateObject("ADSystemInfo")

Const MODE_DFS2000 = "(Windows Server 2000 mode)"
Const MODE_DFS2008 = "(Windows Server 2008 mode)"
Const MODE_ERROR  = "(Could not determine mode)"

strDomainComponents = objRootDSE.Get("DefaultNamingContext")
strDomainFQDN = objSysInfo.DomainDNSName

Set objDFS = GetObject("LDAP://CN=Dfs-Configuration,CN=System," & strDomainComponents & "")
For Each dfsNameSpace In objDFS
  On Error Resume Next
    If IsEmpty(dfsNameSpace.Get("msDFS-SchemaMajorVersion")) Then
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_DFS2000
    ElseIf Not IsEmpty(dfsNameSpace.Get("msDFS-SchemaMajorVersion")) Then
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_DFS2008
      WScript.Echo "\\" & strDomainFQDN & "\" & dfsNameSpace.CN & vbTab & MODE_ERROR
    End If
  On Error Goto 0