Cross Forest User Lookup LDAP Query Script

January 19, 2011 Leave a comment

I had a requirement to query users over an external trust between two AD forests, so here it is:

Set rootDSE = GetObject("LDAP://rootDSE")
Set objConnection = CreateObject("ADODB.Connection")
Set objCommand = CreateObject("ADODB.Command")
objConnection.Provider = "ADSDSOObject"
objConnection.Open "Active Directory Provider"
Set objCommand.ActiveConnection = objConnection

objCommand.Properties("Searchscope") = ADS_SCOPE_SUBTREE
objCommand.CommandText = "SELECT * FROM 'LDAP://" _
"dc=corporate,dc=contoso,dc=com' WHERE objectCategory='user'"
Set objRecordSet = objCommand.Execute
Do Until objRecordSet.EOF
Set objType = GetObject(objRecordSet.Fields("ADsPath").Value)
strDistinguishedName = objType.distinguishedName
wscript.echo strDistinguishedName

Does exactly what it says on the tin.

Windows: Internals – Visibility into processes

January 18, 2011 Leave a comment

Any Windows Administrator worth their salt will eventually have a requirement to use Microsofts Debugging Tools for Windows (Unless their employers don’t mind them squashing & redeploying machines in the event of a BSOD/ASR).

A quick search on your favourite search engine or the Microsoft site for Debugging Tools for Windows will provide you with a link to the debugging tools download page where you can select current or previous versions depending on the platform you’re troubleshooting.

I have often needed to examine certain processes while performing analysis of resource issues.  I’ll cover quite a few utilities that I use for this in the future, but this post will be dedicated to the Task List Viewer utility: TList.exe.

TList is a CLI utility (I much prefer working in the CLI rather than the GUI) that can be used to gather information about processes running on a computer.

The following TList option displays a process tree that shows processes as the children of the process that created them.

c:\>tlist /t
System Process (0)
System (4)  
  smss.exe (404)    
    csrss.exe (452)    
    winlogon.exe (476) NetDDE Agent      
      services.exe (520)        
        svchost.exe (700)        
        svchost.exe (724)        
        svchost.exe (864)        
        svchost.exe (888)        
        spoolsv.exe (996)        
        scardsvr.exe (1040)        
        alg.exe (1172)        
        tievxx.exe (1200) ATI video bios poller        
        InoRpc.exe (1248)        
        InoRT.exe (1264)        
        InoTask.exe (1308)        
        mdm.exe (1392)        
        dllhost.exe (2780)      
  lsass.exe (532)      
  rundll32.exe (500)
explorer.exe (328) Program Manager  
  WLANMON.exe (1728) TI Wireless LAN Monitor  
  ISATRAY.EXE (1712) IsaTray
  cmmon32.exe (456)  
  WINWORD.EXE (844) Tlist.doc - Microsoft Word
  explore.exe (2096) Platform SDK - CreateThread

TList can search for processes by name, PID or even patterns if you’re not sure of the process name.  It can also provide a wealth of information about processes such as which DLL or which module was loaded by which processes.

A full listing of TList command line switches can be found here.

NB: It should be noted that processes are not programs.  In Windows Internals 4th edition Mark Russinovich & David Solomon (men who know!) state that a process is a container for a set of resources, specifically:

  • Private virtual address space,
  • An executable program,
  • A list of open handles to system resources,
  • An access token and
  • A process ID.

Query Last Logon for all Active Directory Users in any Domain

January 17, 2011 Leave a comment

I had a requirement to ascertain how many users in a domain hadn’t logged on in the last 3 months:

Option Explicit
Dim objRootDSE, strConfig, adoConnection, adoCommand, strQuery
Dim adoRecordset, objDC
Dim strDNSDomain, objShell, lngBiasKey, lngBias, k, arrstrDCs()
Dim strCN, dtmDate, objDate, objList, strUser
Dim strBase, strFilter, strAttributes, lngHigh, lngLowr

' Use a dictionary object to track latest lastLogon for each user.

Set objList = CreateObject("Scripting.Dictionary")

objList.CompareMode = vbTextCompare

' Obtain local Time Zone bias from machine registry.
' This bias changes with Daylight Savings Time.
Set objShell = CreateObject("Wscript.Shell")
lngBiasKey = objShell.RegRead("HKLM\System\CurrentControlSet\Control\" _
    & "TimeZoneInformation\ActiveTimeBias")
If (UCase(TypeName(lngBiasKey)) = "LONG") Then
    lngBias = lngBiasKey
ElseIf (UCase(TypeName(lngBiasKey)) = "VARIANT()") Then
    lngBias = 0
    For k = 0 To UBound(lngBiasKey)
        lngBias = lngBias + (lngBiasKey(k) * 256^k)
End If

' Determine configuration context and DNS domain from RootDSE object.

Set objRootDSE = GetObject("LDAP://RootDSE")

strConfig = objRootDSE.Get("configurationNamingContext")
strDNSDomain = objRootDSE.Get("defaultNamingContext")

' Use ADO to search Active Directory for ObjectClass nTDSDSA.
' This will identify all Domain Controllers.
Set adoCommand = CreateObject("ADODB.Command")
Set adoConnection = CreateObject("ADODB.Connection")
adoConnection.Provider = "ADsDSOObject"
adoConnection.Open "Active Directory Provider"
adoCommand.ActiveConnection = adoConnection

strBase = "<LDAP://" & strConfig & ">"
strFilter = "(objectClass=nTDSDSA)"
strAttributes = "AdsPath"
strQuery = strBase & ";" & strFilter & ";" & strAttributes & ";subtree"

adoCommand.CommandText = strQuery
adoCommand.Properties("Page Size") = 100
adoCommand.Properties("Timeout") = 60
adoCommand.Properties("Cache Results") = False

Set adoRecordset = adoCommand.Execute
' Enumerate parent objects of class nTDSDSA. Save Domain Controller
' AdsPaths in dynamic array arrstrDCs.
k = 0
Do Until adoRecordset.EOF
    Set objDC = _
    ReDim Preserve arrstrDCs(k)
    arrstrDCs(k) = objDC.DNSHostName
    k = k + 1

' Retrieve lastLogon attribute for each user on each Domain Controller.
For k = 0 To Ubound(arrstrDCs)
    strBase = "<LDAP://" & arrstrDCs(k) & "/" & strDNSDomain & ">"
    strFilter = "(&(objectCategory=person)(objectClass=user))"
    strAttributes = "sAMAccountName,lastLogon"
    strQuery = strBase & ";" & strFilter & ";" & strAttributes _
        & ";subtree"
    adoCommand.CommandText = strQuery
    On Error Resume Next
    Set adoRecordset = adoCommand.Execute
    If (Err.Number <> 0) Then
        On Error GoTo 0
        Wscript.Echo "Domain Controller not available: " & arrstrDCs(k)
        On Error GoTo 0
        Do Until adoRecordset.EOF
            strCN = adoRecordset.Fields("sAMAccountName").Value
            On Error Resume Next
            Set objDate = adoRecordset.Fields("lastLogon").Value
            If (Err.Number <> 0) Then
                On Error GoTo

                dtmDate = #1/1/1601#
               On Error GoTo 0
                lngHigh = objDate.HighPart
                lngLow = objDate.LowPart
                If (lngLow < 0) Then
                    lngHigh = lngHigh + 1
                End If
                If (lngHigh = 0) And (lngLow = 0) Then
                    dtmDate = #1/1/1601#
                    dtmDate = #1/1/1601# + (((lngHigh * (2 ^ 32)) _
                        + lngLow)/600000000 - lngBias)/1440
                End If
            End If
            If (objList.Exists(strCN) = True) Then
                If (dtmDate > objList(strCN)) Then
                    objList.Item(strCN) = dtmDate
                End If
                objList.Add strCN, dtmDate
            End If
    End If

' Output latest lastLogon date for each user.
For Each strUser In objList.Keys    
    If (objList.Item(strUser) = #1/1/1601#) Then        
        Wscript.Echo strUser & ",Never"    
        Wscript.Echo strUser & "," & objList.Item(strUser)    
    End If

 ' Clean up.ado

Many thanks to ADSI God: Richard Mueller for this!

Disparate DNS Platform Woes

January 16, 2011 Leave a comment

I hope this never happens to you as a DNS administrator, but in the event that you find yourself in the woeful position of having identical discretely maintained legacy DNS zones, this slightly convoluted process may assist you in synchronising the two zones across platforms until such times as you’re able to migrate to one unified solution.  I will create a better solution in the future, but I was exploring different scripting technologies for this one (I now love Perl by the way, it’s ugly, but it works!).

Bear in mind that while I’m primarily a Windows bod, I have a great affinity for Linux/UNIX utilities. Also, I’ve used Googles public DNS IP address, so you won’t be able to get a zone transfer from it.

Firstly a batch file (AXFR.bat) to pull a zone transfer from one DNS platform and copy it to a Windows server that will run on a Windows box with dig, grep & perl installed:

@echo off

:: Get domain AXFR & output to file:
dig @ -t axfr > C:\DNS\Scripts\AXFR.txt 

:: Harvest records and exclude anything with a dash:
grep "host.*" C:\DNS\Scripts\AXFR.txt | grep -v "-" > C:\DNS\Scripts\Filtered_AXFR.txt

:: Convert the file into a format usable by the batch file that will add them to MS DNS:
perl > C:\DNS\Scripts\records.txt 

:: Copy the prepared file to an MS DNS server:
xcopy C:\DNS\Scripts\records.txt \\\C$\DNS\Scripts\ /y

As this will output the following format in the AXFR.txt text file:

; <<>> DiG 9.3.2 <<>> @ -t axfr ; (1 server found) 
;; global options: printcmd	 86400	IN	SOA 8308331 21600 3600 604800 600 86400	IN	A 86400	IN	A	 86400	IN	NS	 86400	IN	NS;;	 86400	IN	SOA	ns1. 8308331 21600 3600 604800 600

;; Query time: 312 msec
;; WHEN: Sat Jan 15 08:30:01 2011
;; XFR size: 7 records (messages 5)

I used grep to filter the results into the Filtered_AXFR.txt file and called the following Perl script (

open(DATA, "Filtered_AXFR.txt");
while (<DATA>) {
@linefields = split(/\s+/);
if (@linefields) { print "$linefields[0];$linefields[@linefields-2];$linefields[@linefields-1]\n"; }

This will output the records.txt file in the following format that a batch file on the Windows server can use to insert records into MS DNS.;;

The next step moves to your Windows DNS server.  This batch file (DNS_Insert.bat) backs up the zone, creates or deletes and adds records from the records.txt file copied to the Windows DNS server by the previous batch file, logs the transactions, emails the support team by calling a CDO VBScript (email.vbs) and deletes logs older than one year:

:: Export zone to C:\DNS\Scripts\Backups before update
dnscmd /ZoneExport MS_DNS_Pre_Update_Zone_%date:~-4,4%%date:~-7,2%%date:~0,2%.txt
echo %date% %time% >> C:\DNS\Scripts\Logs\Export-Move.log
move /Y C:\WINDOWS\system32\dns\MS_DNS_Pre_Update_Zone_*.txt C:\DNS\Scripts\Backups\ >> C:\DNS\Scripts\Logs\Export-Move.log

:: Bulk delete and insert A records matching the contents of C:\DNS\Scripts\records.txt on DC1
for /f "tokens=1-3 delims=;" %%a in (records.txt) do (dnscmd /RecordDelete %%a %%b /f
dnscmd /RecordAdd %%a %%b %%c) >> C:\DNS\Scripts\Logs\Add_RR_2_MSDNS_%date:~-4,4%%date:~-7,2%%date:~0,2%.log

:: Export zone to C:\DNS\Scripts\Backups after update
dnscmd /ZoneExport MS_DNS_Post_Update_Zone_%date:~-4,4%%date:~-7,2%%date:~0,2%.txt
echo %date% %time% >> C:\DNS\Scripts\Logs\Export-Move.log
move /Y C:\WINDOWS\system32\dns\MS_DNS_Post_Update_Zone_*.txt C:\DNS\Scripts\Backups\ >> C:\DNS\Scripts\Logs\Export-Move.log

:: Email Support team to advise which records have been created
cscript C:\DNS\Scripts\Scripts\email.vbs

cd Logs\
:: Delete logs on a rolling yearly basis
:: Ascertain date 364 days ago
set yyyy=

set $tok=1-3
for /f "tokens=1 delims=.:/-, " %%u in ('date /t') do set $d1=%%u
if "%$d1:~0,1%" GTR "9" set $tok=2-4
for /f "tokens=%$tok% delims=.:/-, " %%u in ('date /t') do (
for /f "skip=1 tokens=2-4 delims=/-,()." %%x in ('echo.^|date') do (
    set %%x=%%u
    set %%y=%%v
    set %%z=%%w
    set $d1=
    set $tok=))

if "%yyyy%"=="" set yyyy=%yy%
if /I %yyyy% LSS 100 set /A yyyy=2000 + 1%yyyy% - 100

set CurDate=%mm%/%dd%/%yyyy%

set dayCnt=%1

if "%dayCnt%"=="" set dayCnt=364

set /A dd=1%dd% - 100 - %dayCnt%
set /A mm=1%mm% - 100

if /I %dd% GTR 0 goto DONE
set /A mm=%mm% - 1
if /I %mm% GTR 0 goto ADJUSTDAY
set /A mm=12
set /A yyyy=%yyyy% - 1


if %mm%==1 goto SET31
if %mm%==2 goto LEAPCHK
if %mm%==3 goto SET31
if %mm%==4 goto SET30
if %mm%==5 goto SET31
if %mm%==6 goto SET30
if %mm%==7 goto SET31
if %mm%==8 goto SET31
if %mm%==9 goto SET30
if %mm%==10 goto SET31
if %mm%==11 goto SET30
REM ** Month 12 falls through

set /A dd=31 + %dd%



set /A dd=30 + %dd%



set /A tt=%yyyy% %% 4

if not %tt%==0 goto SET28

set /A tt=%yyyy% %% 100

if not %tt%==0 goto SET29

set /A tt=%yyyy% %% 400

if %tt%==0 goto SET29


set /A dd=28 + %dd%



set /A dd=29 + %dd%



if /I %mm% LSS 10 set mm=0%mm%
if /I %dd% LSS 10 set dd=0%dd%

:: Copy logs less than 364 days ago into a text file to exclude and delete the rest

echo >> Logs_less_than_a_year_old.txt
for /f "tokens=*" %%a IN ('xcopy C:\DNS\Scripts\Logs\*.log /d:%mm%-%dd%-%yyyy% /L /I null') do if exist %%~nxa echo %%~nxa >> C:\DNS\Scripts\Logs\Logs_less_than_a_year_old.txt
for /f "tokens=*" %%a IN ('xcopy C:\DNS\Scripts\Logs\*.log /L /I /EXCLUDE:C:\DNS\Scripts\Logs\Logs_less_than_a_year_old.txt null') do if exist "%%~nxa" del "%%~nxa"

:: Copy exported zone files less than 364 days ago into a text file to exclude and delete the rest
echo >> Files_less_than_a_year_old.txt
for /f "tokens=*" %%a IN ('xcopy C:\DNS\Scripts\Backups\*.txt /d:%mm%-%dd%-%yyyy% /L /I null') do if exist %%~nxa echo %%~nxa >> C:\DNS\Scripts\Logs\Files_less_than_a_year_old.txt
for /f "tokens=*" %%a IN ('xcopy C:\DNS\Scripts\Backups\*.txt /L /I /EXCLUDE:C:\DNS\Scripts\Logs\Files_less_than_a_year_old.txt null') do if exist "%%~nxa" del "%%~nxa"

The CDO VBScript called by the previous batch file reads the last modified date of the records.txt file and copies it and the contents into an email to your support team:

'These constants are defined to make the code more readable
Const ForReading = 1, ForWriting = 2, ForAppending = 8
Dim fso, f, BodyText, arrLines, LineCount, ObtainFile, CreateDate
Set fso = CreateObject("Scripting.FileSystemObject")
'Open the file for reading
Set f = fso.OpenTextFile("C:\DNS\Scripts\Filtered_AXFR.txt", ForReading)
'The ReadAll method reads the entire file into the variable BodyText
BodyText = f.ReadAll
'Split by lines, put into an array
arrLines = Split(BodyText,vbCrLf)
'Use UBound to count the lines
LineCount = UBound(arrLines) + 1
'Close the file
'Get file creation date
Set ObtainFile = fso.GetFile("C:\DNS\Scripts\Filtered_AXFR.txt")
CreateDate = ObtainFile.DateLastModified

Set objMessage = CreateObject("CDO.Message")
objMessage.Subject = "MS DNS Updated with A records from Other DNS Platform"
objMessage.From = ""
objMessage.To = ""
objMessage.TextBody = "The following " & LineCount & " hosts were exported from your other DNS platform on/at: " _
" & CreateDate & " (If this date is more than 24 hours ago, there is a problem with the scheduled task) " _
"and added to MS DNS over the past 10 minutes: " & vbCrLf & vbCrLf & BodyText & vbCrLf & "Exported zone files " _
"can be found here: \\\C$\DNS\Scripts\Backups\ " & vbCrLf & "Update logs can be found here can " _
"be found here: \\\C$\DNS\Scripts\Logs\ "

'==Configure remote SMTP server.

objMessage.Configuration.Fields.Item _
("") = 2

objMessage.Configuration.Fields.Item _
("") = ""

objMessage.Configuration.Fields.Item _
("") = 25


'==Send email

Set f = Nothing
Set fso = Nothing
Set objMessage = Nothing
Set ObtainFile = Nothing	86400	IN	A

Tips: Handy Windows Command Prompt Commands

January 16, 2011 1 comment

This is by no means an exhaustive list, merely ubiquitous commands that I use on a daily basis that will be updated periodically as I can be bothered.

List shares on a file server:

net share

List file locks on a file server:

net file

Close open files from the previous command:

net file <ID> /close

RDP to a specific host:

mstsc.exe /v <FQDN_or-IP-address>

Map network drive:

net use X: \\FQDN\Share$ /persistent:yes

Stop a service:

net stop a <service-name>

Start a service:

net start <service-name>
Categories: Microsoft, Scripting, Tips

Create Red Hat Enterprise Linux Kickstart CD ISO

January 15, 2011 Leave a comment
I had a requirement to create a kickstart CD, so I thought I’d post the basics:
[root@localhost ~]# cd /tmp
[root@localhost tmp]# mkdir bootiso
[root@localhost tmp]# mkdir bootisoks
[root@localhost tmp]# cd /media/CentOS_5.5_Final/images
[root@localhost images]# mount -o loop boot.iso /tmp/bootiso
[root@localhost images]# cp -r /tmp/bootiso/* /tmp/bootisoks
[root@localhost images]# cd /tmp/bootisoks
Kickstart file (ks.cfg) created by me & copied to /tmp/bootisoks/isolinux.
key --skip
# Localisation
lang en_GB.UTF-8
keyboard uk
timezone --utc Europe/London
# Display
xconfig --startxonboot --resolution 800x600
# Network
network --device eth0 --bootproto dhcp
# Security
rootpw --iscrypted <GetYourOwnDamnedHash!>
firewall --enabled --port=22:tcp
authconfig --useshadow --enablemd5
selinux --enforcing
# System
bootloader --location=mbr --driveorder=sda --append="rhgb quiet"
#clearpart --all --initlabel
#part /boot --fstype ext3 --size=50
#part / --fstype ext3 --size=3000 --grow --maxsize=4000
#part swap --size=1024 --grow --maxsize=2048
#part /home --fstype ext3 --size=1000 --grow
@X Window System
@GNOME Desktop Environment
@Graphical Internet
@Development Tools
Creating the ISO:
[root@localhost bootisoks]# mkisofs -o bootks.iso -b isolinux.bin -c -no-emul-boot -boot-load-size 4 -boot-info-table -R -J -v -T isolinux/
INFO:   UTF-8 character encoding detected by locale settings.
Assuming UTF-8 encoded filenames on source filesystem,
use -input-charset to override.
mkisofs 2.01 (cpu-pc-linux-gnu)
Scanning isolinux/
Excluded by match: isolinux/
Excluded: isolinux/TRANS.TBL
Writing:   Initial Padblock                        Start Block 0
Done with: Initial Padblock                        Block(s)    16
Writing:   Primary Volume Descriptor               Start Block 16
Done with: Primary Volume Descriptor               Block(s)    1
Writing:   Eltorito Volume Descriptor              Start Block 17
Size of boot image is 4 sectors -> No emulation
Done with: Eltorito Volume Descriptor              Block(s)    1
Writing:   Joliet Volume Descriptor                Start Block 18
Done with: Joliet Volume Descriptor                Block(s)    1
Writing:   End Volume Descriptor                   Start Block 19
Done with: End Volume Descriptor                   Block(s)    1
Writing:   Version block                           Start Block 20
Done with: Version block                           Block(s)    1
Writing:   Path table                              Start Block 21
Done with: Path table                              Block(s)    4
Writing:   Joliet path table                       Start Block 25
Done with: Joliet path table                       Block(s)    4
Writing:   Directory tree                          Start Block 29
Done with: Directory tree                          Block(s)    1
Writing:   Joliet directory tree                   Start Block 30
Done with: Joliet directory tree                   Block(s)    1
Writing:   Directory tree cleanup                  Start Block 31
Done with: Directory tree cleanup                  Block(s)    0
Writing:   Extension record                        Start Block 31
Done with: Extension record                        Block(s)    1
Writing:   The File(s)                             Start Block 32
Total translation table size: 4925
Total rockridge attributes bytes: 1307
Total directory bytes: 0
Path table size(bytes): 10
Done with: The File(s)                             Block(s)    4674
Writing:   Ending Padblock                         Start Block 4706
Done with: Ending Padblock                         Block(s)    150
Max brk space used 0
4856 extents written (9 MB)
Categories: BASH, Linux, Red Hat, RHCE, RHCT

Tips: Handy Linux Commands

January 15, 2011 1 comment

This is by no means an exhaustive list, merely commands that I use on a daily basis that will be updated periodically as I can be bothered.

Use root complete with variables & script access:

sudo -

Renew DHCP lease:

dhclient -r

Restart networking:

 /etc/init.d/network restart

Check CPU usage/processes:


Find commands:

apropos <command-or-pattern>
man -k <command-or-pattern>

Find files & directories:

locate <pattern>

Display PCI devices:


Display USB devices:


Display storage devices:

fdisk -l

Check for new hardware (Happens at boot time automatically):

kudzu --probe

Check for new hardware & test kudzu (requires rhpxl package):


Gather information from the BIOS:

ownership #(Compaq specific)
vpddecode #(IBM specific)

List drivers/kernel modules:


Manually load a module and its dependencies:

modprobe <modulename>

Display information about a specific module or modules:

modinfo <packagename>

Launch the HAL device manager to examine all devices HAL knows about:

Categories: BASH, Linux, Red Hat, RHCE, RHCT, Tips