I’m often asked if I can include attributes from a user’s Terminal Services Profile tab in their AD properties in AD Info and AD Tidy, but for whatever reason MS decided to store these attributes in a very strange format so it is not easy. I’ve finally spent some time on it and got something working now and thought I would share my results (and frustration) with the rest of the community in case it helps anyone else out.
This is one of the weirdest ways of storing text in Active Directory I have ever seen. I’m sure there must be some backwards compatibility reason for it or something, but looking at it now it just looks such a weird way of doing things.
Active Directory supports multi-valued attributes (i.e. a list of values, rather than just one single value) and it supports unicode strings, so surely if you want to store a list of text values and numbers in an AD attribute you would use a multi-valued unicode string attribute right? Nope, in this case MS decided to use a single valued unicode string attribute but half of the text stored in it (not all of it) is not in unicode format and is instead in some ridiculous encoding of ASCII to hex to pairs of bytes… or something like that. Here’s the documentation:
“for each byte of the input create its ASCII-encoded hexadecimal representation and place this representation in 2 consecutive bytes of the output buffer, the most significant hexadecimal digit first followed by the least significant hexadecimal digit. For example, if the input byte contains the ASCII representation of character ‘A’, the resulting output will be a sequence of two ASCII characters: character ‘4’ followed by character ‘1’ because the hexadecimal representation of a byte that contains the ASCII character ‘A’ is 41. Hence, the output buffer corresponding to the input buffer byte containing character ‘A’ will be a sequence of 2 bytes whose hexadecimal representations are 34 and 31”
So we have the text representation of the hex value of ASCII characters in an attribute that is marked as just being a standard unicode string. No wonder when you try and view it you mostly get hieroglyphics and whitespace characters. Also as mentioned above, not all of the text is encoded in this weird and wonderful way – the name of each property stored inside the userParameters attribute is just regular unicode, but then the value that follows it is encoded as described above.
Anyway, enough complaining, on to the rough solution I came up with last night (I will refactor and improve it soon but for now I just wanted a proof of concept that decodes the values correctly, and it was 2 AM when I finally finished it so I’m sure there is a lot to be improved and error handling that needs to be added):
Public Function GetUserParamsValue(UserParametersText As String, PropertyName As String, IsInteger As Boolean) As String Dim Bytes() As Byte = System.Text.Encoding.Unicode.GetBytes(UserParametersText) Dim Start As Integer = UserParametersText.IndexOf(PropertyName) Dim StartingByteCount As Integer = ((Start + PropertyName.Length) * 2) Dim ValueLength As UInt16 = Convert.ToUInt16(Bytes((Start * 2) - 4)) Dim Counter As Integer = 0 If IsInteger Then Dim HexBytes((CInt(ValueLength / 2) - 1)) As Byte For i As Integer = StartingByteCount To CInt(StartingByteCount + (ValueLength - 1)) Step +2 Dim OriginalCharHex As String = Chr(Bytes(i)) & Chr(Bytes(i + 1)) HexBytes(Counter) = CByte(Integer.Parse(OriginalCharHex)) Counter += 1 Next Return BitConverter.ToUInt32(HexBytes, 0).ToString Else Dim HexBytes(CInt(ValueLength / 2) - 2) As Byte For i As Integer = StartingByteCount To CInt(StartingByteCount + (ValueLength - 3)) Step +2 Dim OriginalCharHex As String = Chr(Bytes(i)) & Chr(Bytes(i + 1)) Dim CharHexDecimal As Integer = Convert.ToInt32(OriginalCharHex, 16) HexBytes(Counter) = CByte(CharHexDecimal) Counter += 1 Next Return System.Text.Encoding.ASCII.GetString(HexBytes) End If End Function
Sorry the code is not very easy to read on here – I’d recommend copy and pasting it in to Visual Studio to see it clearer. The way I’ve made it work in this example function is that you need to pass in the full value of the user’s UserParameters attribute (i.e. CStr(UserDirectoryEntry.Properties(“userParameters”).Value)) and pass in the name of the value inside userParameters that you want to retrieve. The possible value names are listed here and it is fairly easy to see which options in the AD U&C GUI they relate to: http://msdn.microsoft.com/en-us/library/ff635169.aspx
Some values are just a 32 bit unsigned integer, whereas some are text, and I couldn’t find any way of accurately detecting this dynamically so there is an argument to the function where you have to specify if the value you’re retrieving is an Integer or not. Again if you visit the MS documentation link above it specifies which values are integers and which are text.
So here’s an example of using it to get the user’s TS profile path and the number that tells you which setting is set for their TS remote control settings (i.e. allow remote control only with user permission, etc, again listed in the MS link above):
Console.WriteLine(GetUserParamsValue(UserParameters, "CtxShadow", True)) Console.WriteLine(GetUserParamsValue(UserParameters, "CtxWFProfilePath", False))
Like I said, a lot of things I can improve in the function, and sorry for the lack of comments or explanation on exactly what it is doing but I just wanted to post this to help give other people a head start if they are looking to do this (as I spent a lot longer than I’d like to admit trying to get this working).
So to all of the people that have been requesting it, yes you will finally be seeing the attributes from the TS/RDS tab appearing in AD Info and AD Tidy very soon