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.

'**************************************************************************** ' This script created by Chrissy LeMaire ([email protected]) ' Website: https://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 " strEmailTo = "Administrator "

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("https://schemas.microsoft.com/cdo/configuration/sendusing") = 2 'cdoSendUsingPort (1 = local, 3 = Exchange) objCDO.Configuration.Fields.Item("https://schemas.microsoft.com/cdo/configuration/smtpserver") = strSMTPServer objCDO.Configuration.Fields.Item("https://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: 'https://support.microsoft.com/kb/297951 'both are kinda nasty. Set objRSPGID = CreateObject("adodb.recordset") Connstring = "Provider=ADsDSOObject" strSQL = ";(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.