Extracting SQL Queries from .NET Apps with WinDbg

In analysing an application that demonstrated symptoms of the Chatty I/O Antipattern I wanted to extract all the SQL queries performed by application.

This can be done with packet capture tools such as netsh trace and WireShark

If using Wireshark you can use filter tds

You can then expand the TDS query packet and select the relevant item and press Ctrl+Shift+I and right click Apply as Column to see the SQL queries

This will work if the SQL queries are not encrypted.

wireshar

From a .NET application we can also easily get this information using WinDbg.

In this case we use SOSEX extension to simplify creating the breakpoints http://stevestechspot.com/SOSEXV40NowAvailable.aspx

We can then set breakpoints for SQL commands before they are executed then dump and inspect the parameters.

Manually this would look something like this. Typed commands in bold.

0:018> .loadby sos clr
0:018> .load c:\support\sosex.dll
0:018> !mbm *!System.Data.SqlClient.SqlCommand.ExecuteReader
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader() in AppDomain 033332b0.
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior) in AppDomain 033332b0.
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior, System.String) in AppDomain 033332b0.
0:018> !mbm *!System.Data.SqlClient.SqlCommand.ExecuteNonQuery
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() in AppDomain 033332b0.
*** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Data\8eebbeb9e37bb40cf99a4cd06c26e65e\System.Data.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Data\8eebbeb9e37bb40cf99a4cd06c26e65e\System.Data.ni.dll
0:018> g
ModLoad: 15af0000 15f94000 DevExpress.XtraBars.v14.2.dll
ModLoad: 15fa0000 16444000 DevExpress.XtraBars.v14.2.dll
Breakpoint 2 hit
eax=00000000 ebx=1340b228 ecx=5696bfac edx=010fdb0c esi=00000000 edi=010fdb10
eip=56d7be06 esp=010fdad4 ebp=010fdb18 iopl=0 nv up ei ng nz ac pe cy
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200297
System_Data_ni+0x42be06:
56d7be06 8b0d2c139556 mov ecx,dword ptr [System_Data_ni+0x132c (5695132c)] ds:002b:5695132c=08b93648
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Core\9be722d8c68270a743a0aea762f40e2d\System.Core.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\mscorlib\f42a27fd30d9a54b9eb8cba239c2611f\mscorlib.ni.dll
*** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Windows.Forms\c9287eaf630bc35ce3ac0111feec2bdb\System.Windows.Forms.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Windows.Forms\c9287eaf630bc35ce3ac0111feec2bdb\System.Windows.Forms.ni.dll
0:000> !clrstack -p
OS Thread Id: 0x2884 (0)
Child SP IP Call Site
010fdad4 56d7be06 System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior, System.String)
PARAMETERS:
this (0x010fdae0) = 0x1340b228
behavior (<CLR reg>) = 0x00000000
method (0x010fdb20) = 0x052b64ac

010fdb24 56d7bc1e System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(System.Data.CommandBehavior)
PARAMETERS:
this = <no data>
behavior = <no data>

010fdb34 56e63e4d System.Data.Common.DbCommand.ExecuteReader(System.Data.CommandBehavior)
PARAMETERS:
this = <no data>
behavior = <no data>

010fdb3c 0dfb840a System.Data.Entity.Infrastructure.Interception.DbCommandDispatcher.b__c(System.Data.Common.DbCommand, System.Data.Entity.Infrastructure.Interception.DbCommandInterceptionContext`1)
PARAMETERS:
t = <no data>
c = <no data>

etc, etc…

0:000> !DumpObj /d 1340b228
Name: System.Data.SqlClient.SqlCommand
pparamMethodTable: 56a67388
EEClass: 56969038
Size: 164(0xa4) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll
Fields:
MT Field Offset Type VT Attr Value Name
7211fe74 40005a4 4 System.Object 0 instance 00000000 __identity
713c0bfc 40028cc 8 …ponentModel.ISite 0 instance 00000000 site
713b7490 40028cd c ….EventHandlerList 0 instance 00000000 events
7211fe74 40028cb 8d4 System.Object 0 static 055ef010 EventDisposed
72121974 4000ead 60 System.Int32 1 instance 42 ObjectID
7211fad4 4000eae 10 System.String 0 instance 13404ac4 _commandText
56a670e0 4000eaf 64 System.Int32 1 instance 0 _commandType
72121974 4000eb0 68 System.Int32 1 instance 3600 _commandTimeout
56a63cd8 4000eb1 6c System.Int32 1 instance 3 _updatedRowSource
72113798 4000eb2 94 System.Boolean 1 instance 0 _designTimeInvisible
72113798 4000eb3 95 System.Boolean 1 instance 0 _wasBatchModeColumnEncryptionSettingSetOnce
56a64784 4000eb4 70 System.Int32 1 instance 0 _columnEncryptionSetting
56a5d08c 4000eb5 14 …ent.SqlDependency 0 instance 00000000 _sqlDep
72113798 4000eb6 96 System.Boolean 1 instance 0 _inPrepare
72121974 4000eb7 74 System.Int32 1 instance -1 _prepareHandle
72113798 4000eb8 97 System.Boolean 1 instance 0 _hiddenPrepare
72121974 4000eb9 78 System.Int32 1 instance -1 _preparedConnectionCloseCount
72121974 4000eba 7c System.Int32 1 instance -1 _preparedConnectionReconnectCount
56a66b78 4000ebb 18 …rameterCollection 0 instance 1340b2f0 _parameters
56a66cd4 4000ebc 1c …ent.SqlConnection 0 instance 13405fc8 _activeConnection
72113798 4000ebd 98 System.Boolean 1 instance 0 _dirty
56a55e48 4000ebe 80 System.Int32 1 instance 0 _execType
56f66b20 4000ebf 20 …lClient._SqlRPC[] 0 instance 00000000 _rpcArrayOf1
56a63a24 4000ec0 24 …SqlClient._SqlRPC 0 instance 00000000 _rpcForEncryption
56a63d24 4000ec1 28 …t._SqlMetaDataSet 0 instance 00000000 _cachedMetaData
72107a74 4000ec2 2c …bject, mscorlib]] 0 instance 00000000 _reconnectionCompletionSource
56a5dccc 4000ec3 30 …+CachedAsyncState 0 instance 00000000 _cachedAsyncState
72121974 4000ec4 84 System.Int32 1 instance -1 _rowsAffected
72121974 4000ec5 88 System.Int32 1 instance -1 _rowsAffectedBySpDescribeParameterEncryption
56a6678c 4000ec6 34 …tificationRequest 0 instance 00000000 _notification
72113798 4000ec7 99 System.Boolean 1 instance 1 _notificationAutoEnlist
56a54a68 4000ec8 38 …nt.SqlTransaction 0 instance 00000000 _transaction
56a5a538 4000ec9 3c …letedEventHandler 0 instance 00000000 _statementCompletedEventHandler
56a68540 4000eca 40 …ParserStateObject 0 instance 00000000 _stateObj
72113798 4000ecb 9a System.Boolean 1 instance 0 _pendingCancel
72113798 4000ecc 9b System.Boolean 1 instance 0 _batchRPCMode
56962480 4000ecd 44 …PC, System.Data]] 0 instance 00000000 _RPCList
56f66b20 4000ece 48 …lClient._SqlRPC[] 0 instance 00000000 _SqlRPCBatchArray
56f66b20 4000ecf 4c …lClient._SqlRPC[] 0 instance 00000000 _sqlRPCParameterEncryptionReqArray
56957a30 4000ed0 50 …on, System.Data]] 0 instance 00000000 _parameterCollectionList
72121974 4000ed1 8c System.Int32 1 instance 0 _currentlyExecutingBatch
72121974 4000ed2 90 System.Int32 1 instance 0 _currentlyExecutingDescribeParameterEncryptionRPC
72113798 4000ed3 9c System.Boolean 1 instance 0 _isDescribeParameterEncryptionRPCCurrentlyInProgress
72113798 4000ed4 9d System.Boolean 1 instance 0 _internalEndExecuteInitiated
72113798 4000ed5 9e System.Boolean 1 instance 0 <CachingQueryMetadataPostponed>k__BackingField
56a625bc 4000ed6 54 …Server.SmiContext 0 instance 00000000 _smiRequestContext
56a5c79c 4000ed7 58 …+CommandEventSink 0 instance 00000000 _smiEventSink
56a5ab08 4000ed8 5c …DeferedProcessing 0 instance 00000000 _outParamEventSink
72121974 4000eac 678 System.Int32 1 static 42 _objectTypeCount
7212062c 4000ed9 3d4 System.String[] 0 static 051d3e7c PreKatmaiProcParamsNames
7212062c 4000eda 3d8 System.String[] 0 static 051d3ec4 KatmaiProcParamsNames
0:000> !DumpObj /d 13404ac4
Name: System.String
MethodTable: 7211fad4
EEClass: 71ca8d78
Size: 120(0x78) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: Select * from abctax.[FN_GetLockDetails](@DocumentId)
Fields:
MT Field Offset Type VT Attr Value Name
72121974 400026f 4 System.Int32 1 instance 53 m_stringLength
7212051c 4000270 8 System.Char 1 instance 53 m_firstChar
7211fad4 4000274 48 System.String 0 shared static Empty
>> Domain:Value 033332b0:NotInit <<
0:000> !DumpObj /d 1340b2f0
!DumpObj /d 1340b2f0
Name: System.Data.SqlClient.SqlParameterCollection
MethodTable: 56a66b78
EEClass: 56968f2c
Size: 20(0x14) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll
Fields:
MT Field Offset Type VT Attr Value Name
7211fe74 40005a4 4 System.Object 0 instance 00000000 __identity
72113798 4001110 c System.Boolean 1 instance 1 _isDirty
56952d90 4001112 8 …er, System.Data]] 0 instance 1340b32c _items
72120cd0 4001111 4f4 System.Type 0 static 0556405c ItemType
0:000> !DumpObj /d 1340b32c
Name: System.Collections.Generic.List`1[[System.Data.SqlClient.SqlParameter, System.Data]]
MethodTable: 56952d90
EEClass: 71ca56a0
Size: 24(0x18) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
720ad5fc 400187c 4 System.__Canon[] 0 instance 1340b344 _items
72121974 400187d c System.Int32 1 instance 1 _size
72121974 400187e 10 System.Int32 1 instance 1 _version
7211fe74 400187f 8 System.Object 0 instance 00000000 _syncRoot
720ad5fc 4001880 4 System.__Canon[] 0 static <no information>
0:000> !DumpArray /d 1340b344
Name: System.Data.SqlClient.SqlParameter[]
MethodTable: 56a69c20
EEClass: 71ca8ea8
Size: 28(0x1c) bytes
Array: Rank 1, Number of elements 4, Type CLASS
Element Methodtable: 56a67980
[0] 13404b3c
[1] null
[2] null
[3] null
0:000> !DumpObj /d 13404b3c
Name: System.Data.SqlClient.SqlParameter
MethodTable: 56a67980
EEClass: 569690a8
Size: 116(0x74) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\System.Data\v4.0_4.0.0.0__b77a5c561934e089\System.Data.dll
Fields:
MT Field Offset Type VT Attr Value Name
7211fe74 40005a4 4 System.Object 0 instance 00000000 __identity
56a68674 40010ee 8 …qlClient.MetaType 0 instance 00000000 _metaType
56a55a90 40010ef c …ient.SqlCollation 0 instance 00000000 _collation
7211fad4 40010f0 10 System.String 0 instance 00000000 _xmlSchemaCollectionDatabase
7211fad4 40010f1 14 System.String 0 instance 00000000 _xmlSchemaCollectionOwningSchema
7211fad4 40010f2 18 System.String 0 instance 00000000 _xmlSchemaCollectionName
7211fad4 40010f3 1c System.String 0 instance 00000000 _udtTypeName
7211fad4 40010f4 20 System.String 0 instance 00000000 _typeName
72120cd0 40010f5 24 System.Type 0 instance 00000000 _udtType
7211fc1c 40010f6 28 System.Exception 0 instance 00000000 _udtLoadError
7211fad4 40010f7 2c System.String 0 instance 056ce0d8 _parameterName
72120fa4 40010f8 64 System.Byte 1 instance 0 _precision
72120fa4 40010f9 65 System.Byte 1 instance 0 _scale
72113798 40010fa 66 System.Boolean 1 instance 0 _hasScale
56a68674 40010fb 30 …qlClient.MetaType 0 instance 00000000 _internalMetaType
56a64ae4 40010fc 34 …lClient.SqlBuffer 0 instance 00000000 _sqlBufferReturnValue
56a6b70c 40010fd 38 …qlTypes.INullable 0 instance 00000000 _valueAsINullable
72113798 40010fe 67 System.Boolean 1 instance 0 _isSqlParameterSqlType
72113798 40010ff 68 System.Boolean 1 instance 0 _isNull
72113798 4001100 69 System.Boolean 1 instance 0 _coercedValueIsSqlType
72113798 4001101 6a System.Boolean 1 instance 0 _coercedValueIsDataFeed
72121974 4001102 50 System.Int32 1 instance -1 _actualSize
56a5d704 4001103 3c …SqlCipherMetadata 0 instance 00000000 _columnEncryptionCipherMetadata
72113798 4001104 6b System.Boolean 1 instance 0 <HasReceivedMetadata>k__BackingField
72113798 4001105 6c System.Boolean 1 instance 0 <ForceColumnEncryption>k__BackingField
7211fe74 4001106 40 System.Object 0 instance 13404bb0 _value
7211fe74 4001107 44 System.Object 0 instance 1340b2f0 _parent
56a67934 4001108 54 System.Int32 1 instance 0 _direction
72121974 4001109 58 System.Int32 1 instance 0 _size
72121974 400110a 5c System.Int32 1 instance 0 _offset
7211fad4 400110b 48 System.String 0 instance 00000000 _sourceColumn
56a55e10 400110c 60 System.Int32 1 instance 0 _sourceVersion
72113798 400110d 6d System.Boolean 1 instance 0 _sourceColumnNullMapping
72113798 400110e 6e System.Boolean 1 instance 0 _isNullable
7211fe74 400110f 4c System.Object 0 instance 00000000 _coercedValue
0:000> !DumpObj /d 13404bb0
Name: System.Int32
MethodTable: 72121974
EEClass: 71c068fc
Size: 12(0xc) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
72121974 400058c 4 System.Int32 1 instance 1234738 m_value
0:000> !DumpObj /d 056ce0d8
Name: System.String
MethodTable: 7211fad4
EEClass: 71ca8d78
Size: 34(0x22) bytes
File: C:\WINDOWS\Microsoft.Net\assembly\GAC_32\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll
String: DocumentId
Fields:
MT Field Offset Type VT Attr Value Name
72121974 400026f 4 System.Int32 1 instance 10 m_stringLength
7212051c 4000270 8 System.Char 1 instance 44 m_firstChar
7211fad4 4000274 48 System.String 0 shared static Empty

To automate this I created a JavaScript which can be run from WinDbg in the Windows 10 store . This will automatically dump the SQL command and parameters. This code was written just as a prototype so may need adjustments to meet your exact requirements.


"use strict";

function initializeScript()
{
return [new host.apiVersionSupport(1, 3)];
}

function GetDatabaseQuery()
{
var ctl = host.namespace.Debugger.Utility.Control;
var output = ctl.ExecuteCommand("!clrstack -p");
var sqlCommand = false;

var commandReference = "";

for (var line of output)
{
if (line.indexOf("System.Data.SqlClient.SqlCommand.ExecuteReader") >=0 ||
line.indexOf("System.Data.SqlClient.SqlCommand.ExecuteNonQuery") >=0 )
{
sqlCommand = true;
}

if (line.indexOf("this (") >= 0 && sqlCommand == true)
{
commandReference = line.split("=")[1].trim();
break;
}
}

if (commandReference != "")
{
var commandtext = "";
var parameters = "";
output = ctl.ExecuteCommand("!DumpObj /d " + commandReference);
for (var line of output)
{
if (line.indexOf("_commandText")>=0)
{
commandtext = line.substring(60,68);
}

if (line.indexOf("_parameters")>=0)
{
parameters = line.substring(60,68);
}
}

if (commandtext != "")
{
var output = ctl.ExecuteCommand("!DumpObj /d " + commandtext);
var sqlCommand = "";
for (var line of output)
{
if (sqlCommand != "")
{
if (line.indexOf("Fields:") >= 0)
{
break;
}

sqlCommand+="\n" + line;
}
if (line.indexOf("String:") >= 0)
{
sqlCommand = line.substring(13);
}
}
host.diagnostics.debugLog("SqlCommand=" + sqlCommand + "\n");
}

host.diagnostics.debugLog("SqlParameters=");
if (parameters == "00000000" || parameters == "")
{
host.diagnostics.debugLog("NULL\n");
}
else
{
var output = ctl.ExecuteCommand("!DumpObj /d " + parameters);
var items = "";
for (var line of output)
{
if (line.indexOf("_items") >= 0)
{
items = line.substring(60,68);
break;
}
}

if (items != "")
{
var output = ctl.ExecuteCommand("!DumpObj /d " + items );
for (var line of output)
{
if (line.indexOf("_items") >= 0)
{
items = line.substring(60,68);
break;
}
}

var output = ctl.ExecuteCommand("!DumpArray /d " + items );
for (var line of output)
{
if (line.startsWith("["))
{
var val = line.split(" ")[1].trim();
if (val != "null")
{
var arrayOutput = ctl.ExecuteCommand("!DumpObj /d " + val )
var _paramName = "";
var _value = "";
for (var item of arrayOutput)
{
if (item.indexOf("_parameterName") >= 0)
{
_paramName = item.substring(60,68);
}

if (item.indexOf("_value") >= 0)
{
_value = item.substring(60,68);
}
}

arrayOutput = ctl.ExecuteCommand("!DumpObj /d " + _paramName);
var ParameterName = "";
var ParameterValue = "";
for (var item of arrayOutput)
{
if (item.indexOf("String:") >= 0)
{
ParameterName = item.substring(13);
break;
}
}

arrayOutput = ctl.ExecuteCommand("!DumpObj /d " + _value);
for (var item of arrayOutput)
{
if (item.indexOf("String:") >= 0)
{
ParameterValue = item.substring(13);
break;
}

if (item.indexOf("instance") >= 0)
{
ParameterValue = item.substring(60,68).trim();
break;
}
}

host.diagnostics.debugLog(ParameterName + "=" + ParameterValue + ";");

}
}
}
host.diagnostics.debugLog("\n");
}

}

}

}
function invokeScript()
{
//
// Insert your script content here. This method will be called whenever the script is
// invoked from a client.
//
// See the following for more details:
//
// https://aka.ms/JsDbgExt
//
GetDatabaseQuery()
}
;

This can then be fully automated to collect all the SQL queries into a log file. This log file can then be processed so data is easier to view , such as in excel.

Example usage for this scenario:

0:020> .logopen C:\support\sql.log
Opened log file ‘c:\support\sql.log’
0:020> .loadby sos clr
0:020> .load c:\support\sosex.dll
0:020> !mbm *!System.Data.SqlClient.SqlCommand.ExecuteReader “.scriptrun C:\support\SqlExtractors.js;!clrstack;g”
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader() in AppDomain 02683c10.
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior) in AppDomain 02683c10.
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteReader(System.Data.CommandBehavior, System.String) in AppDomain 02683c10.
*** WARNING: Unable to verify checksum for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Data\8eebbeb9e37bb40cf99a4cd06c26e65e\System.Data.ni.dll
*** ERROR: Module load completed but symbols could not be loaded for C:\WINDOWS\assembly\NativeImages_v4.0.30319_32\System.Data\8eebbeb9e37bb40cf99a4cd06c26e65e\System.Data.ni.dll
0:020> !mbm *!System.Data.SqlClient.SqlCommand.ExecuteNonQuery “.scriptrun C:\support\SqlExtractors.js;!clrstack;g”
Breakpoint set at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() in AppDomain 02683c10.
0:020> g

< Reproduce Problem>

0:0020> .logclose

PowerShell script to analyse the generated log file and produce a CSV:

</pre>
$data = Get-Content C:\support\sql.log
$outputFileName = C:\support\sql.csv
$table = New-Object System.Data.DataTable
$table.Columns.Add("Query")
$table.Columns.Add("Parameters")
$table.Columns.Add("Code Location")

$currentSQLCommand = ""

$currentStack = ""
$currentSQLParam = ""
$sqlParam = $false
$stack = $false
$sqlCommand = ""
$c = 0
ForEach ($line in $data)
{
$c++
Write-Progress -Activity "Extracting SQL data" -PercentComplete ($c* (100/$data.Count))
if ($stack -eq $true)
{
if ($line.Contains("JavaScript script"))
{
$stack = $false
#uncomment these 3 lines and change "Abc." to prefix of desired DLL, if you want only the first item in stack matching
#$i = $currentStack.IndexOf("Abc.")
#$j = $currentStack.IndexOf("`r",$i)
#$currentStack = $currentStack.Substring($i,$j-$i)
[void]$table.Rows.Add($sqlCommand,$currentSQLParam,$currentStack)
}
else
{
if ($line.Length -gt 18)
{
$currentStack += "`r`n" + $line.Substring(18)
}
}
}
if ($line.Contains("Child SP IP Call Site"))
{
$stack = $true
$currentStack = ""
}

if ($sqlParam -eq $true)
{
if ($line.Contains("OS Thread Id"))
{
$sqlParam = $false
}
else
{
if ($line.Contains("="))
{
$currentSQLParam += $line.Substring(0,$line.IndexOf(";")+1)
}
}
}

if ($line.StartsWith("SqlParameters="))
{
$sqlCommand = $currentSQLCommand
$currentSQLParam = ""
$currentSQLCommand = ""
$sqlParam = $true
}
if ($currentSQLCommand -ne "")
{
$currentSQLCommand+="`r`n" + $line
}
if ($line.StartsWith("SqlCommand="))
{
$stack = ""
$currentSQLCommand = $line.Substring(11)
}
}

$table | Export-Csv -NoTypeInformation -Path $outputFilename
<pre>

 

 

 

About chentiangemalc

specializes in end-user computing technologies. disclaimer 1) use at your own risk. test any solution in your environment. if you do not understand the impact/consequences of what you're doing please stop, and ask advice from somebody who does. 2) views are my own at the time of posting and do not necessarily represent my current view or the view of my employer and family members/relatives. 3) over the years Microsoft/Citrix/VMWare have given me a few free shirts, pens, paper notebooks/etc. despite these gifts i will try to remain unbiased.
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a comment