Scripting. Stuff. (By Froosh)

February 2, 2009

Tracking AD last logon times

Filed under: Active Directory, Windows — Tags: , , , — Froosh @ 1:13 pm

Do some quick searches, and you will find that lastLogon is a non-replicated property that indicates when each individual Domain Controller authenticated an account, and (for Windows Server 2003 Forest Functional level domains) the lastLogonTimestamp is a replicated attribute that only updates every 14 days (minimum), but will at least be consistent across all DCs.

This is reasonably useful information, and can be found just about everywhere. But what about non-Windows client logons? What happens if clients are authenticated via LDAP or Mac OSX? How can I trust that “inactive accounts” based on lastLogonTimestamp are truly inactive?

These are my results, your mileage may vary, and my results may be inconsistent with other opinions.

My test:

  1. create a new, never used, account in AD
  2. authenticate to ad with the new account from an LDAP client (apache 2.2 on an Ubuntu server in this case)
  3. check the lastLogon and lastLogonTimestamp attributes

My results:

  • lastLogon attributes were not updated on any DC. FAIL.
  • lastLogonTimestamp was updated, then replicated to all DCs. Success.


  • Reports based on the lastLogonTimestamp can be trusted to provide a list of inactive accounts, as long as you are reporting on accounts older than 14 days. This applies to LDAP, and most likely other non-Windows client authentications.
  • Reports based on the lastLogon attribute can be much more accurate, but only for logons from Windows-based clients.  In a heterogenous environment, they are not to be trusted.

January 6, 2009

Byte Array and SID manipulation class and component

Filed under: VBScript — Froosh @ 11:41 am

In a move that is a little like Marge re-using the 1 stylish dress she found, I’ve wrapped my old Byte() and SID manipulation code into a VBScript Class and a Windows Script Component.

The class code can be directly used within your VBScript VBS/WSF file, or included in a WSF package with the <script src=…> tag.

The Windows Script Component needs to be installed on each machine it will be used with and registered with “regsvr32 scrobj.dll /n /i:<path to wsc file>”.

Alternatively, it may be possible to use the WSC without registering it as in the following snippet:

Set objComponent = GetObject(“script:c:\COM\MyComponent.wsc”)

This loads the first (or only) <component> section and is usable the same way as calling CreateObject(“MyComponent”). One upside of this is that normally component registration requires administrator rights, but using a WSC this way does not.


August 22, 2006

Old passwords are still valid with Windows Server 2003 SP1

Filed under: Windows — Froosh @ 12:22 pm

We’ve been pulling our hair out for a while because the software we use to provide e-mail synchronisation services for pocket pc users keeps providing very strange results when staff change their passwords.

The main symptom was that the sync software would still work without prompting for an updated password. It appeared to keep using the old password for a while, and then refuse to sync because it’s cached password was invalid. For some reason it did not recognise a password change.

The software vendor was utterly useless, sending us revision after revision of the software and claiming complete fixes. After a few fruitless cycles, they provided a work-around in which a single service account would be used to sync with their mailboxes. I wasn’t happy with their answer of “But you’re the only company reporting this issue” and they were not willing to pursue it.

Thankfully, I now at least have part of the answer: MS KB 906305 Windows Server 2003 Service Pack 1 modifies NTLM network authentication behavior.

This document states:

After you install Windows Server 2003 SP1, domain users can use their old password to access the network for one hour after the password is changed. Existing components that are designed to use Kerberos for authentication are not affected by this change.

And so it becomes clear. The sync software is using NTLM auth rather than Kerberos and so the old passwords are still accepted by the servers. It also explains why we could not reproduce it by logging into the machines interactively, as that uses Kerberos.

What I don’t have a satisfactory answer to is: When we logged a call with Microsoft Premier Support (yes, this costs a lot), they claimed that it was impossible, there was no way that the old passwords could be accepted.

Now I just have to cross my fingers and hope that the vendor will do something to fix our problem properly. I’m not expecting much.

March 29, 2006

Free VB.Net, C# and J# Compilers

Filed under: C#, J#, VB.NET — Froosh @ 12:10 pm

I was somewhat surprised to find that the Microsoft .NET Frameworks come supplied with compilers for VB.NET, C# and J#.

As an amateur programmer and general software hack, I really haven't been interested in buying Microsoft's Viral Visual Studio suite just to be able to compile my various micro-applications. Yes, I know the new "Express" versions are free – at the moment that is.

I'm surprised because in this and a few other vaguely remembered ways, the Windows OS has been approaching the "Batteries Included" status that most of the *NIX variants offer. (I love the term Batteries Included – both because I have a child who often need extra batteries purchased for a new toy, and becuase of it's use in describing the Python language)

So, where are they and how can you use them?

C:\Windows\Microsoft.Net\Framework\v2.0.50727\ (or any other version you have installed)

  • csc.exe
  • jsc.exe
  • vbc.exe

Run them from a command line to see all the options available.

Below are some examples of code and how to compile it:

VB.Net Console "Hello World"
C# Windows "Hello World"

October 21, 2005

Hex SID to Decimal SID Translation

Filed under: VBScript — Froosh @ 11:46 am

Update: I’ve rolled this into a VBScript class and a Windows Script Component.

If you’ve ever played with SIDs in VBScript, you will have needed to convert a binary SID to the usual slightly-human-readable decimal format (e.g. S-1-5-21-xxxxxxxx-etc). Since I couldn’t find any examples of VBScript to do this, I’ve had to create it myself – and I post it here to use, poke fun at, or ignore at your leisure 😀

The two main references I needed to make this possible are from two of the very best programming blogs I read: The Old New Thing: How do I convert a SID between binary and string forms? and Fabulous Adventures In Coding: Integer Arithmetic in VBScript, Part Two. Also useful are the MSDN article on Well Known SIDs and my previous posting here VBScript vs. Byte().

The guts of the script is based on this excerpt from The Old New Thing:

If your SID is S-1-5-21-2127521184-1604012920-1887927527-72713, then your raw hex SID is 010500000000000515000000A065CF7E784B9B5FE77C8770091C0100

This breaks down as follows:
01 S-1
05 (seven dashes, seven minus two = 5)
000000000005 (5 = 0x000000000005, big-endian)
15000000 (21 = 0x00000015, little-endian)
A065CF7E (2127521184 = 0x7ECF65A0, little-endian)
784B9B5F (1604012920 = 0x5F9B4B78, little-endian)
E77C8770 (1887927527 = 0X70877CE7, little-endian)
091C0100 (72713 = 0x00011c09, little-endian)

And without further ado, here is the code to convert a hex string representation of a SID to the decimal format (and back again). Also included is the little helper function to swap between big- and little-endian representations. Note: Lines may be wrapped – it’s not worth my effort right now to format it nicely. Leave a comment if you want me to e-mail it to you.

Function ConvertHexStringToSidString(strHex)
  Const intSidVersionLength = 2
  Const intSubAuthorityCountLength = 2
  Const intAuthorityIdentifierLength = 12
  Const intSubAuthorityLength = 8

  Dim intStringPosition, bytSidVersion, lngAuthorityIdentifier, bytSubAuthorityCount, lngTempSubAuthority

  intStringPosition = 1

  bytSidVersion = CByte("&h" & Mid(strHex, intStringPosition, intSidVersionLength))
  intStringPosition = intStringPosition + intSidVersionLength

  bytSubAuthorityCount = CByte("&h" & Mid(strHex, intStringPosition, intSubAuthorityCountLength))
  intStringPosition = intStringPosition + intSubAuthorityCountLength

  lngAuthorityIdentifier = CLng("&h" & Mid(strHex, intStringPosition, intAuthorityIdentifierLength))
  intStringPosition = intStringPosition + intAuthorityIdentifierLength

  ConvertHexStringToSidString = "S-" & bytSidVersion & "-" & lngAuthorityIdentifier

  Do Until bytSubAuthorityCount = 0
    lngTempSubAuthority = CLng("&h" & EndianReverse(Mid(strHex, intStringPosition, intSubAuthorityLength)))
    intStringPosition = intStringPosition + intSubAuthorityLength

    If lngTempSubAuthority < 0 Then lngTempSubAuthority = lngTempSubAuthority + 2^32

    ConvertHexStringToSidString = ConvertHexStringToSidString & "-" & lngTempSubAuthority

    bytSubAuthorityCount = bytSubAuthorityCount - 1
End Function

Function ConvertSidStringToHexString(strSID)
  Const intSidVersionLength = 2
  Const intSubAuthorityCountLength = 2
  Const intAuthorityIdentifierLength = 12
  Const intSubAuthorityLength = 8

  Dim intCounter, arrSplitSid, bytSidVersion, lngAuthorityIdentifier, bytSubAuthorityCount, dblTempSubAuthority

  arrSplitSid = Split(strSID, "-")

  bytSidVersion = CByte(arrSplitSid(LBound(arrSplitSid) + 1))
  bytSubAuthorityCount = CByte(UBound(arrSplitSid) - LBound(arrSplitSid) - 2)
  lngAuthorityIdentifier = CLng(arrSplitSid(LBound(arrSplitSid) + 2))

  ConvertSidStringToHexString = Right(String(intSidVersionLength, "0") & Hex(bytSidVersion), intSidVersionLength)
  ConvertSidStringToHexString = ConvertSidStringToHexString & Right(String(intSubAuthorityCountLength, "0") & Hex(bytSubAuthorityCount), intSubAuthorityCountLength)
  ConvertSidStringToHexString = ConvertSidStringToHexString & Right(String(intAuthorityIdentifierLength, "0") & Hex(lngAuthorityIdentifier), intAuthorityIdentifierLength)

  For intCounter = (LBound(arrSplitSid) + 3) to UBound(arrSplitSid)
    dblTempSubAuthority = CDbl(arrSplitSid(intCounter))
    If dblTempSubAuthority > 2^31 - 1 Then dblTempSubAuthority = dblTempSubAuthority - 2^32
    ConvertSidStringToHexString = ConvertSidStringToHexString & EndianReverse(Right(String(intSubAuthorityLength, "0") & Hex(dblTempSubAuthority), intSubAuthorityLength))
End Function

Function EndianReverse(strHex)
  Dim intCounter

  For intCounter = Len(strHex) to 1 Step - 2
    EndianReverse = EndianReverse & Mid(strHex, intCounter - 1, 2)
End Function

Enjoy! (I may comment these in the future for clarity, but don’t count on it.)

Update 4-Sep-2007: Wow, I must have had a bad day when I posted this – it was completely borkened. Updated with (I hope) actual working script and the mirror function to convert them back to strings. Oh, and using the new built-in syntax highlighter. Look out for the &amp; &lt; and &gt; in the code above as they need to be replaced with actual & < and > symbols. It seems the editor is munging the code when the syntax highlighter instructions tell us not to worry about the HTML entities.

Update 8-May-2008: Looks like the syntax highlighter was updated some time in the past to handle the HTML elements and the ‘copy to clipboard’ option also works perfectly.

Update 12-Aug-2008: Sigh.  It seems the the code keeps getting messed up.  I’m still happy to e-mail out the scriptlet, though sometimes I’m a bit laggy.

September 22, 2005

VBScript vs. Byte()

Filed under: VBScript — Froosh @ 9:43 am

In a recent fight with VBScript I was attempting to read SID’s from the logged in users Access Token and translate them to group names. In the process, objUser.Get(“tokenGroups”) returns a Variant() array of Byte() arrays.

Easy you say, just do something like for each bytPart in Byte() … except that VBScript doesn’t actually know how to work with a Byte(). It knows Byte and Array(), just not how they work together.

After much head-scratching and googling, the secret was revelead in a snippet of code attributed to Richard Mueller, a MS MVP in Scripting and ADSI (found on Michael Harris’ blog). And the secret is: Byte() arrays are really just byte strings (as opposed to unicode/char strings).

Knowing that, the next minor secret is how to work with byte strings. Unsurprisingly, the answer is in the documentation. Read any of the string functions (Len, InStr, Mid, etc) and pay attention to any Note: sections that mention a *B variant of the function. e.g. LenB, InStrB, MidB

ConvertByteArrayToHexString = ""
For intCounter = 1 to LenB(arrBytes)
  ConvertByteArrayToHexString = ConvertByteArrayToHexString & Right("0" & Hex(AscB(MidB(arrBytes, intCounter, 1))), 2)

So now you know. Happy Byte()-ing!

Create a free website or blog at