netnerds.net

30Sep/091

IIS: Authentication Fails with Error Code 2148074254

Recently, we migrated a web application to a SharePoint server. The web application is a custom built ASP/ASPX app that supports Integrated Windows Authentication. We were able to run the application successfully using Anonymous Access but we were unable to authenticate from remote computers when Anonymous Access was disabled.

I checked the NTFS permissions and double checked the IIS properties for both the Web site and the Virtual directory and everything was setup properly. Different types of domain accounts, even Domain Admins, were attempted and none were able to login successfully from remote machines. Logging in from the server itself, however, worked just fine.

Our SharePoint sites which Windows Authentication and Kerberos were authenticating properly, so it wasn't the server. I checked the web server logs and saw HTTP 401 errors failing with the IIS specific code "2148074254." Searching the web turned up nothing useful. I tried a registry change and even rebooted the server to no avail.

I checked the server again -- local login works just fine, remote logins do not. I recalled experiencing something similar with another application I'd written and the problem had to do with Kerberos' restrictions on double-hop authentication. Maybe this site just needs an SPN?

I created a new application pool, assigned the web application to that pool and ran the pool as a domain user. I then created an SPN using the hostname of the newly migrated site.

Setspn.exe -A HTTP/vieval.domain.com domain\iisservice
Setspn.exe -A HTTP/vieval domain\iisservice

Success! Once the registration was complete, we were able to login to the web application. I find it surprising that the Kerberos setting is server-wide. Anyone know the reasons why?

Posted by: Chrissy   Filed under: Active Directory, IIS, Security, SharePoint 1 Comment
19Jan/086

FIX: 'Cannot Write Pam Settings' when Joining a Windows Domain in SuSE 10.3

Today I attacked my 2008 technical to do list and setup a subversion server for backups/source control. It was actually pretty darn easy in SUSE 10.3. After I got it going, I wondered if I could have it automatically authenticate against my HOME domain. So, using SuSE's menu driven interface YaST, I easily added my Linux machine to my Windows domain.

Initially, YaST wasn't able to find or join the domain. This happens sometimes in Windows clients too when:
1. In TCP/IP, the DNS settings are pointed to servers outside of the domain
2. The fully qualified domain name (ex. corp.windomain.com) is not given when joining the domain
3. The FQDN is not listed as a DNS search suffix

After adjusting /etc/resolv.conf to reflect my fully qualified domain name, YaST made it surprisingly easy to find and join the domain. But right as it was finishing up, it ran into the error "cannot write pam settings." I looked around the web and saw about 2 other people had the same problem but no solution was offered. After poking around, I noticed that "pam-smb" was not installed. Generally, SuSE will automatically detect when rpms need to be added but in this case it didn't.

In order to get it all working, I added pam-smb, samba-winbind and krb5-client then I easily plugged into my Windows 2003 domain. Years ago, I tried to do something similar and it seemed to work but I was never able to login via SSH. I'm pretty sure I didn't prefix the domain (in proper case, at that) when attempting to login. Knowing that, I was successfully able to login to my Linux machine using a Windows domain login this time around.

login as: HOME\testuser
Using keyboard-interactive authentication.
Password: *****************
Creating directory '/home/HOME/testuser'.
Creating directory '/home/HOME/testuser/public_html'.
Creating directory '/home/HOME/testuser/bin'.
Creating directory '/home/HOME/testuser/Documents'.
Have a lot of fun...
HOME\testuser@subversion:~>

Awesome! This is much easier than doing user mapping with NIS.

Posted by: Chrissy   Filed under: Active Directory, Linux, Networking 6 Comments
26Jul/076

Powershell: Working with Passwords

When creating a new Active Directory user from the command line in PowerShell, you will likely find yourself using Read-Hosts's asSecureString switch when entering the password.

$password = Read-Host "Enter password" -AsSecureString

Next, you'll probably look around the Internets for a few hours or so trying to figure out how to change the password of the newly created user. You will soon discover that the user creation process in PowerShell 1.0 isn't very straightfoward and it even requires a specific order for proper account creation. First, you create the account, then you set some basic properties, next you call SetInfo(), and finally you invoke setPassword using the follwing syntax:

$newUser.psbase.Invoke("SetPassword",$password)

Now you may find yourself with the following exception: Exception calling "Invoke" with "2" argument(s): "Exception has been thrown by the target of an invocation." Originally, this post mentioned using toString() to address the problem but PowerShell team member Lee Holmes wrote to let me know that the password was changed literally to System.Security.SecureString. He also said that "there is no really easy way to convert a secure string to plain text - on purpose. Since a SecureString is supposed to prevent plain text from littering your computer's memory, converting it to plain text defeats the purpose."

My primary reason for using asSecureString is to encode the string into asterisks when typing it at the prompt. So Lee gave me two ways to convert the password to be used while invoking SetPassword. Note that unless you are using a secure LDAP channel, the password will be sent over the network in clear text.

$temporaryCredential = New-Object System.Management.Automation.PsCredential "None",$password
$newUser.psbase.Invoke("SetPassword",$temporaryCredential.GetNetworkCredential().Password)

Or, alternatively:

$temporaryCredential = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($password))$newUser.psbase.Invoke("SetPassword",$temporaryCredential)

If you continue to see this exception, check to make sure that the password you entered meets your domain's password complexity requirements.

Posted by: Chrissy   Filed under: Active Directory, PowerShell, Security 6 Comments
26Jul/070

PowerShell 1.0: Find the Fully Qualified Domain Name of Current Active Directory Domain

So I'm making the move to PowerShell. It's painful learning such alien (to me) concepts but books like Lee Holmes' PowerShell: The Definitive Guide help a ton. I was fortunate enough to be the editor for Chapters 1-5 and got a sneak preview. It's a fantastic book and can't wait to receive the title, complete with indexes! For now, I'm searching both the 36 Word documents and the sample code for solutions using Vista's built-in search functions.

My first task, which I'll explain in later posts, includes some AD stuff. One tiny part of the equation is dynamically finding the FQDN of the current Active Directory domain. This should be easy! Using RootDSE, it's super easy to find out the DistinguishedName or even the FQDN of the domain controller being queried, so wouldn't there be a similar entry for FQDN of the whole domain? Apparently not (or if it's there, I can't find it). I've spent the morning and part of last night digging through LDAP filters, looking on Google for examples of objectcategory=crossref, dnsroot, dnshostname, etc. But after finding this useful codeplex page, I played around with GetCurrentDomain() and realized that finding the DNS hostname for an AD domain all boils down to this one line:

$strDomainDNS = [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain().Name

Or this one liner, as Lee just suggested to me

$strDomainDNS = $env:USERDNSDOMAIN

Ahh! So it was that easy. Why, then, did it take 8 hours to find that? Now I wonder how to get the FQDN of any domain. Being a newb is hard work.

If you're wondering what dnshostname is for, it's to resolve the local machine you are working with. Michael at brnets.com provides the following example embedded in a big ol Exchange script:

$rootDSE = [adsi]"LDAP://RootDSE"
$DCDNShostname = $rootDSE.dnsHostName
$DCDNShostname

Oh, as a bonus, here's some another query you may find helpful. I started with benp's script then made it a little skinnier (and more prone to errors! ;) )

Search for Active Directory User Object in the Current Domain

$domain = New-Object DirectoryServices.DirectoryEntry
$search = [System.DirectoryServices.DirectorySearcher]$domain
$search.Filter = "(&(objectClass=user)(sAMAccountname=Chrissy))"
$user = $search.FindOne().GetDirectoryEntry()
$user.Name

My original code made reference to GetDirectoryEntry() but John Brennan suggested looking for alternative approaches. Thanks for the tip, John!

Posted by: Chrissy   Filed under: Active Directory, PowerShell No Comments
4Apr/0711

Fix Slow External DNS Resolution in Microsoft/Active Directory DNS Server

Aw yaille! I just wrote up a whole explanatory blog post then lost it so this one will likely be brief... or not.

Recently, we found that one of our DNS servers was resolving external hostnames unacceptably slow -- about 5 seconds, give or take. The resolution was so slow, in fact, that all of the clients hopped on to the secondary DNS server thinking that the primary had gone down. After logging on to the server to troubleshoot, I could see that:

1. Pinging external hostnames worked well after the hostname resolved. So did traceroute.
2. Caching wasn't working at all
2. Other AD DNS servers on the network were resolving external hostnames quickly
3. The root servers were all there but I deleted and reloaded them anyway
    - Note: you can actually load root servers from a root server which is cool
4. Internal hostname resolution was extremely fast
5. A reboot didn't help (you may laugh but this has solved severe AD problems for me)

Because the other AD Servers were picking up the slack, I decided to come back to it later. I went out and had dinner with a friend then returned after a few hours. Upon logging back on to the Internets, an old network admin friend messaged me. I told him what I was seeing and he said he had the exact same issue a few months back. After a few minutes of trying to recall the solution, he asked "Have you checked your forwarders?" I'd glanced at them but went back to check again. And there it was.. an entry to a machine we'd recently taken down (long story..). I knew the moment I saw the IP that it was the problem. I removed the entry and noticed the forwarded query timeout was equal to *drumroll* 5 seconds.

Finding that solution was impossible on the Internet because of the super general terms: Slow DNS Resolution External Active Directory. Nothing really worked for me so hopefully this post will help others in the future.

Update: A colleague of mine mentioned spyware interfering with proper DNS functionality resulting in intermittent resolution problems. So that's something you may want to check with a netstat -bn which shows you what programs are using which ports. DNS uses UDP port 53.

Posted by: Chrissy   Filed under: Active Directory, Networking, Windows 11 Comments
12Feb/0726

Active Directory: E-mail Notification for Newly Added Users and Computers

Recently, someone asked if there was a way to be notified when servers have been added to an Active Directory domain. I looked around the Internet and it seems there's not a direct way to do this without some large software package. So in lieu of having an instant notification, I created a script that tallies up newly added user and computer accounts and emails the admin with all the info. This script is initially setup to be run once a day, but you can modify to whatever frequency you want.

I really expected this to take at most a couple hours to write but parsing through all the data turned out to take a heckofa lot of time. From converting the desired comparison date to UTC to parsing the AD attribute memberOf, I spent a good day working on this. I learned a lot, though and found this page which details the AD schema to be very helpful.

If you need anything more than this, you may want to consider an enterprise-type administration package such as Microsoft's MOM.

ADAddedUsersNComputers.vbs

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Website: http://netnerds.net/
'
' ADAddedUsersNComputers.vbs
'
' This script Checks AD for any additions made to Users or Computers
' in the past 24 hours. The time interval to check can be changed below.
'
' NO WARRANTIES, USE THIS AT YOUR OWN RISK, etc.
'*****************************************************************************

'Please modify these four settings
strSMTPServer = "myexchangeserver"
strEmailFrom = "Administrator  <me@mydomain.com>"
strEmailTo = "Administrator <me@mydomain.com>"

strTimeInUTC = CompareDateUTCConvert("h",-24) 'This is the same syntax as dateAdd(). The example will get new users/computers added in the past 24 hours.

'Unless you want to change the domain to check or the format of the emailed info, nothing below really needs to be modified.
On Error Resume Next
numPersonCount = 0
numComputerCount = 0

Set objAdRootDSE = GetObject("LDAP://RootDSE")
Set objRS = CreateObject("adodb.recordset")
varConfigNC = objAdRootDSE.Get("defaultNamingContext")
strConnstring = "Provider=ADsDSOObject"
strWQL = "SELECT ADsPath FROM 'LDAP://" & varConfigNC & "' WHERE createTimeStamp > '" & strTimeInUTC & "' and (objectCategory = 'Person' or objectCategory = 'Computer')"

objRS.Open strWQL, strConnstring
Do until objRS.eof
Set objADUserOrComputer = GetObject(objRS.Fields.Item(0))
strObjectCategory = ParseDN(objADUserOrComputer.objectCategory)

Select Case strObjectCategory
Case "Person"
numPersonCount = numPersonCount + 1
If Len(objADUserOrComputer.displayName) > 0 Then strUserMsg = strUserMsg & vbCrLf & "displayName = " & objADUserOrComputer.displayName
'strUserMsg = strUserMsg & vbCrLf & "distinguishedName = " & objADUserOrComputer.distinguishedName
strUserMsg = strUserMsg & vbCrLf & "sAMAccountName = " & objADUserOrComputer.sAMAccountName
strUserMsg = strUserMsg & vbCrLf & "sAMAccountType = " & SAMAccountTypetoName(objADUserOrComputer.sAMAccountType)
strUserMsg = strUserMsg & vbCrLf & "whenChanged = " & objADUserOrComputer.whenChanged
strUserMsg = strUserMsg & vbCrLf & "whenCreated = " & objADUserOrComputer.whenCreated
strUserGroups = ParseMemberOf(objADUserOrComputer.memberOf,objADUserOrComputer.PrimaryGroupID)
strUserMsg = strUserMsg & vbCrLf & "Member Of: " & strUserGroups
If Len(objADUserOrComputer.userPrincipalName) > 0 Then strUserMsg = strUserMsg & vbCrLf & "userPrincipalName = " & objADUserOrComputer.userPrincipalName
strUserMsg = strUserMsg & vbCrLf
Case "Computer"
numComputerCount = numComputerCount + 1
strCompMsg = strCompMsg & vbCrLf & "dNSHostName = " & objADUserOrComputer.dNSHostName
strCompMsg = strCompMsg & vbCrLf & "isCriticalSystemObject = " & objADUserOrComputer.isCriticalSystemObject
strCompMsg = strCompMsg & vbCrLf & "operatingSystem = " & objADUserOrComputer.operatingSystem
strCompMsg = strCompMsg & vbCrLf & "operatingSystemServicePack = " & objADUserOrComputer.operatingSystemServicePack
strCompMsg = strCompMsg & vbCrLf & "operatingSystemVersion = " & objADUserOrComputer.operatingSystemVersion
If InStr(objADUserOrComputer.rIDSetReferences,"Domain Controller") > 0 Then strCompMsg = strCompMsg & vbCrLf & "Domain Controller = Yes"
If Len(objADUserOrComputer.description) > 0 Then strCompMsg = strCompMsg & vbCrLf & "description = " & objADUserOrComputer.description
If Len(objADUserOrComputer.machineRole) > 0 Then strCompMsg = strCompMsg & vbCrLf & "machineRole = " & objADUserOrComputer.machineRole
If Len(objADUserOrComputer.physicalLocationObject) > 0 Then strCompMsg = strCompMsg & vbCrLf & "physicalLocationObject = " & ParseDN(objADUserOrComputer.physicalLocationObject)
strCompMsg = strCompMsg & vbCrLf
End Select
objRS.movenext
Set objADUserOrComputer = Nothing
Loop
objRS.close
Set objRS = Nothing
Set objAdRootDSE = Nothing

If Len(strUserMsg) > 0 Then strEmailMessage = strEmailMessage & "--------- USERS ---------" & vbCrLf & strUserMsg & vbCrLf
If Len(strCompMsg) > 0 Then strEmailMessage = strEmailMessage & "--------- COMPUTERS ---------" & vbCrLf & strCompMsg
If Len(strUserMsg) = 0 And Len(strCompMsg) = 0 Then strEmailMessage = "No users or computers have been added in the last 24 hours."

Set objCDO = CreateObject("CDO.Message")
    objCDO.Subject = "Users Added: " & numPersonCount & ". Computers Added: " & numComputerCount & "."
    objCDO.From = strEmailFrom
    objCDO.To = strEmailTo
    objCDO.TextBody = strEmailMessage
    objCDO.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2 'cdoSendUsingPort (1 = local, 3 = Exchange)
    objCDO.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strSMTPServer
    objCDO.Configuration.Fields.Item("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = 25
    objCDO.Configuration.Fields.Update
    objCDO.Send
    set objCDO = Nothing

Function CompareDateUTCConvert(dateAddInterval,compareNumber)
'Wow, this is a lil complex. So createTimestamp is in UTC format.
'So first we grab your machine's time bias and then apply it.
'Next, we adjust the date to the one you specified above (now()-24hours by default)
'Finally, we parse the final date to UTC format ie. 20070207032200.0Z

Set objSWbemServices = GetObject("winmgmts:" & "{impersonationLevel=impersonate}!\.\root\cimv2")
Set colTimeZone = objSWbemServices.ExecQuery("SELECT * FROM Win32_TimeZone")
For Each objTimeZone in colTimeZone
strBias = objTimeZone.Bias
Next
Set colTimeZone = Nothing
Set objSWbemServices = Nothing
strCompareDate = dateAdd(dateAddInterval,compareNumber,now())
strUTCCompare = DateAdd("n",strBias*(-1),strCompareDate)
CurrentUTC = Year(strUTCCompare) & Right("0" & Month(strUTCCompare),2) & Right("0" & Day(strUTCCompare),2)
CurrentUTC = CurrentUTC & Right("0" & Hour(strUTCCompare),2) & Right("0" & Minute(strUTCCompare),2) & Right("0" & Second(strUTCCompare),2) & ".0Z"
CompareDateUTCConvert = CurrentUTC
End Function

Function ParseDN(strDN)
'Take a DN and extract what we want then make it pretty.
arrDN = split(strDN,",") 'CN=Example-Thing,CN=Whatever,CN=Etc
strDN = right(arrDN(0),len(arrDN(0))-3) 'CN=Example-Thing -> Example-Thing
strDN = replace(strDN,"-"," ") 'Example Thing
ParseDN = strDN
End Function

Function ParseMemberOf(memberof,primarygroupid)
'This shows what groups a person belongs to.
'The output of memberof changes depending on
'how many groups the user is a member of, etc.
Select Case TypeName(memberof)
Case "String" ParseMemberOf = ParseDN(memberof)
Case "Empty" ParseMemberOf = PrimaryGroupIDtoName(primarygroupid,varConfigNC)
Case "Variant()"
For each groupDN in memberof
strUserGroups = strUserGroups & vbCrLf & ParseDN(groupDN)
Next
ParseMemberOf = strUserGroups
Case Else ParseMemberOf = "Unknown"
End Select
End Function

Function SAMAccountTypetoName(theType)
'Just makin it more useful...
Select Case theType
Case 268435456 SAMAccountTypetoName = "Group Object"
Case 268435457 SAMAccountTypetoName = "Non-Security Group Object"
Case 536870912 SAMAccountTypetoName = "Alias Object"
Case 536870913 SAMAccountTypetoName = "Non-Security Alias Object"
Case 805306368 SAMAccountTypetoName = "Normal User Account"
Case 805306369 SAMAccountTypetoName = "Machine Account"
Case 805306370 SAMAccountTypetoName = "Trust Account"
Case 1073741824 SAMAccountTypetoName = "App Basic Group"
Case 1073741825 SAMAccountTypetoName = "App Query Group"
Case 2147483647 SAMAccountTypetoName = "Account Type Max"
Case Else SAMAccountTypetoName = "Unknown"
End Select
End Function

Function PrimaryGroupIDtoName(PGID,varConfigNC)
'Ugh.. the alternative to this function can be found here:
'http://support.microsoft.com/kb/297951
'both are kinda nasty.
  Set objRSPGID = CreateObject("adodb.recordset")
    Connstring = "Provider=ADsDSOObject"
    strSQL = "<ldap://" & varConfigNC & ">;(objectCategory=group);distinguishedName,primaryGroupToken,name;subtree"
objRSPGID.Open strSQL, Connstring
      If not objRSPGID.eof and not objRSPGID.bof Then
       Do until objRSPGID.eof Or Len(strGroupName) > 0
       If PGID = objRSPGID("primaryGroupToken") Then strGroupName = objRSPGID("name")
       objRSPGID.movenext
     Loop
      End If
     objRSPGID.close
  Set objRSPGID = Nothing
  If Len(strGroupName) = 0 Then strGroupName = "Unknown"
  PrimaryGroupIDtoName = strGroupName
End Function

To schedule this, save the above code as ADAddedUsersNComputers.vbs in C:\scripts (for ex.) and use Scheduled Tasks to run the following command: %windir%\system32\wscript.exe C:\scripts\ADAddedUsersNComputers.vbs. I suggest running it daily at the end of each workday.

Posted by: Chrissy   Filed under: Active Directory, VBScript 26 Comments
9Jan/0714

VBScript: Enumerate All SQL Servers on a Domain

So if you have SQL Server installed locally, you're lucky enough to have access to SQLDeeMO. You can then easily enumerate SQL Servers with the following code. Note: If you do decide to use VBScript and SQLDeeMO, you will need to remove the "ee" in the script below. I didn't want to put in the actual object name so that people searching Google for sites that do not contain the phrase SQLDeeMO will still find my site.

SQLDeeMO VBScript Action

Set objSQLDMOApp = CreateObject("SQLDeeMO.Application")
    Set objSQLList = objSQLDeeMOApp.ListAvailableSQLServers()
    For i = 1 To objSQLList.Count
       MsgBox objSQLList.Item(i)
    Next

    Set objSQLList = Nothing
    Set objSQLDeeMOApp = Nothing

But if you don't have SQL Server installed locally.. here's a hack that grabs the name of all Windows Servers in AD and then checks their registry for instances of SQL Server.

Enumerate All SQL Servers and Instance Names on a Domain

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Modifications by Gregory Jones (greg@fuzsh.com) and Radsky
' Website: http://netnerds.net/
'
' This script finds all SQL Servers and their instances that are members of an AD domain
' and running any Windows Server version
'
' Run this script with admin privs on any computer within a domain
'
' This script has been tested on Windows Server 2003 and Server 2008.
' The newest script REQUIRES SQL Native Client to get the Version.
'
' "What it does"
' 1. Gathers all machines in a domain that are running a Windows Server OS (NT, 2000, 2003, 2008, etc)
' 2. Pings them to see if they are available
' 3. If they do respond to pings, it checks their registry to see if they have the proper SQL keys
' 4. If the key does exist, it then enumerates the instances (including default)
' 5. And then it goes get the version and architecture
'
' NO WARRANTIES, USE THIS AT YOUR OWN RISK, etc.
'*****************************************************************************
'on error resume next

Set objAdRootDSE = GetObject("LDAP://RootDSE")
Set objRS = CreateObject("adodb.recordset")
set objFS = CreateObject("Scripting.FileSystemObject")
Set objOutputText = objFS.CreateTextFile("sqlServers.txt")

Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
Const HKEY_LOCAL_MACHINE = &H80000002
varConfigNC = objAdRootDSE.Get("defaultNamingContext")
strConnstring = "Provider=ADsDSOObject"
strWQL = "SELECT * FROM 'LDAP://" & varConfigNC & "' WHERE objectCategory= 'Computer' and OperatingSystem = 'Windows*Server*' "

objRS.Open strWQL, strConnstring
Do until objRS.eof
Set objServer = GetObject(objRS.Fields.Item(0))
strServerName = objServer.CN
Set colItems = objWMIService.ExecQuery("Select * from Win32_PingStatus Where Address = '" & objServer.DNSHostName & "'")
For Each objItem in colItems
If objItem.StatusCode = 0 Then 'The Computer is Pingable
Set objRegistry = GetObject("winmgmts:{impersonationLevel=impersonate}!\\" & strServerName & "\root\default:StdRegProv")
strKeyPath = "SOFTWARE\Microsoft\Microsoft SQL Server"
strValueName = "InstalledInstances"
objRegistry.GetMultiStringValue HKEY_LOCAL_MACHINE,strKeyPath, strValueName,arrValues
If IsNull(arrValues) = 0 Then 'It's a SQL Server! Enumerate it's instances
For Each strValue In arrValues
if lcase(strValue) <> "mssqlserver" then strServerName = strServerName & "\" & strValue
strMsg = strServerName & vbtab & GetSQLServerVersion(strServerName)
objOutputText.WriteLine strMsg
Next
End If
Set objRegistry = Nothing
End If
Set objServer = Nothing
Next
objRS.movenext
Loop
objRS.close
objOutputText.close

Set objOutputText = nothing
Set objWMIService = Nothing
Set objRS = Nothing
Set objAdRootDSE = Nothing

Msgbox "Done!"
'####
Function GetSQLServerVersion(serverName)
on error resume next
strConn = "Driver={SQL Server};Server=" & serverName & ";Database=master"
strsql = "SELECT @@version"

set rs = createobject("adodb.recordset")
rs.Open strsql, strConn, 1, 1

if err.Number <> 0 then
GetSQLServerVersion = "Port blocked (Likely Desktop / Express)"
err.Clear
else
if not rs.eof and not rs.bof then
strVersion = rs(0).Value
if inStr(strVersion,vbLf) > 0 Then strVersion = left(strVersion,inStr(strVersion,vbLf))
GetSQLServerVersion = strVersion
else
GetSQLServerVersion = "Unknown Version"
end If
end if
rs.close
set rs = nothing
End Function '//Function GetSQLServerVersion(serverName)

If you are looking for the version of each SQL Server.. you can either query it with SELECT @@VERSION or it may be easily found in the registry. It's 10:47pm and I'm still at work so I'm heading out ;)

Posted by: Chrissy   Filed under: Active Directory, VBScript 14 Comments
9Jan/0718

VBScript: Use an LDAP Query to Find All Windows Servers on a Domain

Damn, the ADsDSOObject rocks! This script, which weighs in at less than 20 lines, finds all machines running any form of Windows Server on a given domain. Note that this script isn't useful in finding domain controllers, but rather any machine running Windows Server.

Awesome

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Website: http://netnerds.net/
'
' This script finds all machines running Windows Server (NT, 2000, 2003) in AD
'
'Msgbox output provides server name and OS version.
'
' NO WARRANTIES, USE THIS AT YOUR OWN RISK, etc.
'*****************************************************************************

Set objAdRootDSE = GetObject("LDAP://RootDSE")
Set objRS = CreateObject("adodb.recordset")

  varConfigNC = objAdRootDSE.Get("defaultNamingContext")
  strConnstring = "Provider=ADsDSOObject"
  strWQL = "SELECT * FROM 'LDAP://" & varConfigNC & "' WHERE objectCategory= 'Computer' and OperatingSystem = 'Windows*Server*'"
  objRS.Open strWQL, strConnstring
    Do until objRS.eof
   Set objServer = GetObject(objRS.Fields.Item(0))
strServerName = objServer.CN
strOperatingSystem = objServer.OperatingSystem
MsgBox strServerName & " is running " & strOperatingSystem
objRS.movenext
   Set objServer = Nothing
    Loop
  objRS.close

Set objRS = Nothing
Set objAdRootDSE = Nothing

Also, I found this nice reference of Command One Liners while searching the web. Totally handy!

Posted by: Chrissy   Filed under: Active Directory, Networking, VBScript 18 Comments
9Jan/072

VBScript: Find All Exchange Servers in Active Directory

My friend Sharfa and I were exchanging some of our favorite code snippets and he showed me one for enumerating Exchange Servers in Active Directory. I dug the code but wanted to try to see if I could use my Recordset/ADsDSOObject skrills to shorten the code. The outcome isn't any shorter but it does get the version, so that's cool. Thanks, Sharfa, for pointing me towards the WMI Exchange_Server thing, too. :-D

Hit It

'****************************************************************************
' This script created by Chrissy LeMaire (clemaire@gmail.com)
' Website: http://netnerds.net/
'
' This script finds all Exchange Servers in AD. Includes Exchange Version.
'
' Run this script with admin privs on any computer within a domain.
'
' This script has only been tested on Windows Server 2003
'
' NO WARRANTIES, USE THIS AT YOUR OWN RISK, etc.
'*****************************************************************************

Set objAdRootDSE = GetObject("LDAP://RootDSE")
Set objRS = CreateObject("adodb.recordset")

varConfigNC = objAdRootDSE.Get("configurationNamingContext")

  strConnstring = "Provider=ADsDSOObject"
  strSQL = "SELECT * FROM 'LDAP://" & varConfigNC & "' WHERE objectCategory='msExchExchangeServer'"
  objRS.Open strSQL, strConnstring
    Do until objRS.eof
Set objServer = GetObject(objRS.Fields.Item(0))
   Call getExchangeInfo(objServer.CN)
Set objServer = Nothing
     objRS.movenext
    Loop
  objRS.close

Set objRS = Nothing
Set objAdRootDSE = Nothing

Sub getExchangeInfo(strServerName)
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!" & strServerName & "\\ROOT\MicrosoftExchangeV2")
Set colItems = objWMIService.ExecQuery("Select * from Exchange_Server")

For Each objItem in colItems
MsgBox UCase(objItem.Name) & " (" & objItem.FQDN & ") is running Exchange " & objItem.ExchangeVersion
Next

Set colItems = Nothing
Set objWMIService = Nothing
End Sub
Posted by: Chrissy   Filed under: Active Directory, Exchange, VBScript 2 Comments
13Dec/060

VBScript: Kerberos, Delegation, IIS and User Authentication

Recently, I wanted to write a web-based front end to AD User Management for our help desk. The way that I set it up apparently broke some Kerberos delegation rules and even though Microsoft wrote a step-by-step guide on how to get IIS and Kerberos delegation going, the solution didn't work for me. If I turned off Anonymous access and authenticated as myself against a remote webserver (local webserver totally worked), I would get the error 0x80040E37 - Table Does Not Exist. The table does exist, of course, I just don't have the rights to see it. Well, I do but not in the "double hop" manner that I'm attempting it. Kerberos sees that IIS != me.. IIS is only pretending to be me and it doesn't approve.

After a few days of tinkering, this is the solution I decided on. I set the ASP page in IIS to Anonymous Access but ran that access as a user with the privleges to make changes to user accounts. This can be dangerous in more than one way -- if a lesser-privleged user were to somehow have the ability to edit that page, he or she could use it to run any script under the power of that user. So I ensured that unauthorized users were not able to access that file. But now how can I detect who's running that page? Request.servervariables("REMOTE_USER") would be useless since the page is running as that privleged user. I considered what client variables I did have access to and realized that request.servervariables("REMOTE_HOST") would be the handiest. I figured that, using WMI and the IP address, it would be possible to figure out what user is actively logged into the client machine. Sure enough...

Function getLoggedInUser(ipaddr)
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!"& ipaddr & "\root\cimv2")
Set colSessions = objWMIService.ExecQuery("Select * from Win32_LogonSession Where LogonType = 2 OR LogonType = 10" )

For Each objSession in colSessions
Set colList = objWMIService.ExecQuery("Associators of {Win32_LogonSession.LogonId=" & objSession.LogonId & "} Where AssocClass=Win32_LoggedOnUser Role=Dependent")
For Each objItem in colList
getLoggedInUser = lcase(objItem.Name)
Next
Next
End Function

This script, which was derived from a tek-tips.com post, worked flawlessly! FWIT, LoginType 2 is console and 10 is remote desktop.

Posted by: Chrissy   Filed under: Active Directory, IIS, VBScript No Comments