Monday, March 29, 2010 8:43 PM by roger

This is surely no surprise for many people: you can actually debug managed code with WinDbg (or CDB, if you prefer that one) by using the SOS extension (that ships with the .Net framework) in the debugger. What's new is that today, Microsoft released Psscor2 which is pretty much an enhanced version of SOS. Now I'm sure that you'll soon find much information on using Psscor2 on Tom's blog. I'm much in favor of Psscor2 myself and therefore I'll try to post useful scripts here as well (if I can come up with some ;-)).

Friday, December 11, 2009 2:13 PM by roger

Tracing OCS components may be vital in troubleshooting various issues you may face in your deployment. On machines where you have OCS components installed, you'll typically find a tool called OCSLogger.exe which allows you to start/stop/view traces of OCS components. However, sometimes this is not enough, for instance when you see problems at the startup of a machine. It's kind of hard to run the GUI if you cannot logon yet. But you can typically run a scheduled task. Or maybe you are — just like me — more like the console guy and thus want to have a script/cmdline tool for everything.

Let's start with the config file used by the script (TraceConfig.xml) which defines the components you want to trace, to what level the traces are supposed to be and some more things. The sample given here traces mostly the components which are useful in troubleshooting issues related to the Response Group Service of OCS.

 1: <?xml version="1.0" encoding="utf-8"?>
 2: <Config>
 3:     <!--
 4:         Levels:
 5:             TL_FATAL        1
 6:             TL_ERROR        2
 7:             TL_WARN         3
 8:             TL_INFO         4
 9:             TL_VERBOSE      5
10:             TL_NOISE        6
11:
12:         Flags:
13:             TF_COMPONENT    0x00000001
14:             TF_PROTOCOL     0x00000002
15:             TF_CONNECTION   0x00000004
16:             TF_SECURITY     0x00000008
17:             TF_DIAG         0x00000010
18:             TF_AUTH         0x00000020
19:             TF_PARSE        0x00000040
20:             TF_NETWORK      0x00000080
21:             TF_STACKTRACE   0x00000100
22:     -->
23:     <Default Level="6" Flags="0xffff" />
24:     <Paths Tracer="C:\Program Files\Common Files\Microsoft Communications Server 2007 R2\Tracing"
25:            Etl="D:\Tracing"
26:            Log="D:\Tracing"
27:            TmfSearchPath="C:\Program Files\Common Files\Microsoft Communications Server 2007 R2\Tracing">
28:     </Paths>
29:     <Components>
30:         <Component Name="LcsWMI" Enabled="no" />
31:         <Component Name="LcsWMIUserServices" Enabled="no" />
32:  
33:         <Component Name="PowerShell" Enabled="yes" />
34:  
35:         <Component Name="ApplicationServer" Enabled="yes" />
36:  
37:         <Component Name="RgsClientsLib" Enabled="yes" />
38:         <Component Name="RgsCommonLibrary" Enabled="yes" />
39:         <Component Name="RgsDatastores" Enabled="yes" />
40:         <Component Name="RgsDeploymentApi" Enabled="yes" />
41:         <Component Name="RgsDeploymentLibrary" Enabled="yes" />
42:         <Component Name="RgsDiagnostics" Enabled="yes" />
43:         <Component Name="RgsHostingFramework" Enabled="yes" />
44:         <Component Name="RgsMatchMakingService" Enabled="yes" />
45:     </Components>
46: </Config>

I added the most importan trace levels and flags in the comment. Right now, the Default element defines the levels and flags for all components, but there's no reason why you shouldn't be able to do that per component you want to trace.

The PS1 script itself (Tracer.ps1) heavily relies on the OcsTracer.exe tool which also comes with OCS and is typically installed in the same place as OcsLogger.exe. It has four main actions:

  1. Start tracing components
  2. Stop tracing components and format the traces
  3. Format traces of ETL files (e.g. from a different machine)
  4. Show the configuration details from a particular config XML file

  1: <#
  2: .SYNOPSIS
  3:         Starts or Stops tracing of Office Communications Server components.
  4: .DESCRIPTION
  5:         Starts or Stops tracing of Office Communications Server components.
  6: .PARAMETER Action
  7:         The action to perform. Must be one of 'Start', 'Stop', 'Config' or
  8:         'Format'.
  9: .PARAMETER ConfigPath
 10:         The path to the configuration XML file. If not specified,
 11:         "TraceConfig.xml" is used.
 12: .LINK
 13:         This script was originally posted to
 14:         http://www.cymbeline.ch/post/2009/12/11/PowerShell-Script-to-trace-OCS-Components.aspx
 15: .EXAMPLE
 16:         .\Tracer.ps1 Start
 17:
 18:         Starts tracing all the enabled components from the "TraceConfig.xml" file.
 19: .EXAMPLE
 20:         .\Tracer.ps1 Stop
 21:
 22:         Stops tracing all the enabled components from the "TraceConfig.xml" file
 23:         and formats the traces.
 24: .EXAMPLE
 25:         .\Tracer.ps1 Format "MyOtherConfig.xml"
 26:
 27:         Formats the traces of the enabled components from the "MyOtherConfig.xml"
 28:         file with all the settings from the "MyOtherConfig.xml" file.
 29: .EXAMPLE
 30:         .\Tracer.ps1 Config
 31:
 32:         Shows the configuration of the "TraceConfig.xml" file.
 33: #>
 34: param(
 35:     [Parameter(Mandatory=$true)]
 36:     [ValidateSet("Start", "Stop", "Config", "Format", IgnoreCase=$true)]
 37:     [String] $Action,
 38:     [String] $ConfigPath = "TraceConfig.xml"
 39: )
 40:  
 41: $configXml = ([xml](Get-Content $ConfigPath))
 42: $tracerPath = $configXml.Config.Paths.Tracer
 43: $etlDir = $configXml.Config.Paths.Etl
 44: $logDir = $configXml.Config.Paths.Log
 45: $tmfSearchPath = $configXml.Config.Paths.TmfSearchPath
 46:  
 47: # Construct the parameters for the 'Start' command to OcsTracer.exe
 48: function getStartParams()
 49: {
 50:     $ret = @()
 51:  
 52:     $configXml.Config.Components.Component |
 53:         ? {$_.Enabled -eq "yes"} |
 54:         foreach {
 55:             $ret = $ret +
 56:                 ("/Component:" + $_.Name + "," + $configXml.Config.Default.Level +
 57:                     "," + $configXml.Config.Default.Flags + " ")
 58:         }
 59:  
 60:     return $ret
 61: }
 62:  
 63: # Construct the parameters for the 'Stop' command to OcsTracer.exe
 64: function getStopParams()
 65: {
 66:     $ret = @()
 67:  
 68:     $configXml.Config.Components.Component |
 69:         ? {$_.Enabled -eq "yes"} |
 70:         foreach { $ret = $ret + ("/Component:" + $_.Name) }
 71:  
 72:     return $ret
 73: }
 74:  
 75: # Format the ETL files for enabled components to a human readable format
 76: function formatFiles(
 77:     [Parameter(Mandatory=$true)]
 78:     [String] $Timestamp
 79: )
 80: {
 81:     md $logDir\$timestamp -ea silentlycontinue | Out-Null
 82:  
 83:     $configXml.Config.Components.Component |
 84:         ? {$_.Enabled -eq "yes"} |
 85:         foreach {
 86:             $etlFile = $_.Name + ".etl";
 87:  
 88:             if (Test-Path $etlFile)
 89:             {
 90:                 $logFile = $Timestamp + "\" + $Timestamp + "_" + $_.Name + ".log";
 91:  
 92:                 & "$tracerPath\OcsTracer.exe" Format /LogFilePath:"$etlDir\$etlFile" /OutputFile:"$logDir\$logFile" /TmfSearchPath:"$tmfSearchPath" | Write-Verbose
 93:             }
 94:             else
 95:             {
 96:                 Write-Warning "File $etlFile not found.";
 97:             }
 98:         }
 99: }
100:  
101: Write-Host "Using Config File: $ConfigPath"
102: $timestamp = Get-Date -format "yyyy-MM-dd_HH.mm.ss"
103:  
104: if ($Action -eq "start")
105: {
106:     Write-Host "Removing all .etl files ..."
107:     ls $etlDir *.etl | ri
108:  
109:     Write-Host "Start tracing components ..."
110:     $params = getStartParams
111:  
112:     & "$tracerPath\OcsTracer.exe" Start $params /LogFileFolder:"$etlDir" | Write-Verbose
113: }
114: elseif ($Action -eq "stop")
115: {
116:     Write-Host "Stop tracing components ..."
117:     $params = getStopParams
118:  
119:     md $logDir\$timestamp | Out-Null
120:  
121:     & "$tracerPath\OcsTracer.exe" Stop $params /OutputFile:"$logDir\$timestamp\$($timestamp)_All.log" /TmfSearchPath:"$tmfSearchPath" | Write-Verbose
122:  
123:     if (!$?)
124:     {
125:         rd $logDir\$timestamp | Out-Null
126:     }
127:     else
128:     {
129:         Write-Host "Sessions stopped. Start formatting ..."
130:         formatFiles $timestamp
131:     }
132: }
133: elseif ($Action -eq "format")
134: {
135:     Write-Host "Formatting traces from ETL files ..."
136:     formatFiles $timestamp
137: }
138: elseif ($Action -eq "config")
139: {
140:     Write-Host "Default values"
141:     Write-Host "--------------"
142:     $configXml.Config.Default | ft Level,Flags
143:  
144:     Write-Host "Paths"
145:     Write-Host "-----"
146:     $configXml.Config.Paths | fl
147:  
148:     Write-Host "Components"
149:     Write-Host "----------"
150:     $configXml.Config.Components.Component | ft Name,Enabled
151: }
152: else
153: {
154:     Write-Error "Unknown action."
155: }

For samples on how to run the script, please run man .\Tracer.ps1 -Examples Have fun :)

Monday, November 30, 2009 7:54 PM by roger

At work, I have created multiple tools which we used to analyse and fix issues related to certificates that we use with Office Communications Server and their respective private key files. To summarize, a user/service who wants to use the certificate for authentication needs to have read access on the private key. If it doesn't, you'll typically see a strange error which many people don't relate to missing ACLs on the private key file.

Now these days, you don't need to write such tools anymore. PowerShell allows you to pretty much do everything you need in this area. Let's look at the following PS script (let's call it FindPrivateKey.ps1) which accepts a parameter of type System.Security.Cryptography.X509Certificates.X509Certificate2, i.e. a reference to the certificate you want to analyze.

 1: param(
 2:     [Parameter(Mandatory=$true)]
 3:     [System.Security.Cryptography.X509Certificates.X509Certificate2]
 4:     $Certificate
 5: )
 6:  
 7: echo "Looking for private key file of certificate"
 8: echo $Certificate
 9: echo ""
10: echo "The private key file is '$($Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName)'"
11: echo ""
12:  
13: $file = ls $env:userprofile -Filter $Certificate.PrivateKey.CspKeyContainerInfo.UniqueKeyContainerName -Recurse -Force -EA SilentlyContinue
14: echo "It is located at '$($file.FullName)'."

I guess I do not need to mention that you can easily find the certificate you're interested in by running something like

1: cd cert:\CurrentUser\My
2: ls
3: $cert = gci 0DAC31905AEB722D8561BFAF3F3BFD2F551AA197
4: FindPrivateKey.ps1 $cert

where '0DAC31905AEB722D8561BFAF3F3BFD2F551AA197' is simply the thumbprint of the certificate we're interested in. From here on it should be easy to check what ACLs the file has (run $file.GetAccessControl()) and to modify them (run $file.SetAccessControl()).

Wednesday, July 29, 2009 9:08 PM by roger

Sometimes – especially in lab environments – you'll see issues around user authentication with the RGS Agent Tab of Office Communications Server 2007 R2. This post should help you in determining what could be the issue and how to work around it.

First of all, when the OCS 2007 R2 WebComponents get installed on a machine, by default Integrated Windows Authenticated (IWA) for the RGS parts of WebComponents are enabled. We don't require IWA, but this is the recommended setting; anything but Anonymous Authentication should work. If Anonymous Authentication is set for the RGS virtual directory in IIS, you'll find a warning in NT event log about that. In that case, you should turn back on authentication for the virtual directory.

Another problem I've seen a couple of times was as follows: Agent A's credentials are used to sign in with Office Communicator, but the Agent Tab in OC shows the RGS Agent Group memberships of Agent B, or it shows that the "Current User is not an Agent". In this case, you should start tracing the RgsClientsLib component and either wait until the Agent Tab in OC refreshes automatically (this should happen within 30 - 60 seconds) or you can open the tab URL in IE; it's typically something like https://pool-1.contoso.com/Rgs/Clients/Tab.aspx. Then, stop tracing and check out the captured traces for RgsClientsLib. You should now find something along the lines of

Authentication type: [Negotiate]
Authenticated user: [CONTOSO\AgentB]
Authenticated user's SID: [S-1-5-21-2278291046-1170081271-1450921830-1285]
Authenticated user's SID maps to: [efa2cabd-462c-49e4-a021-4dd71bd97ce4]

Please note that I left out the less important information like timestamps etc. here. What you see is that instead of AgentA, AgentB is being authenticated. Usually, this happens when the credentials you pass in to OC are different from the credentials you used to log in to Windows. OC uses the IE engine to render the tabs and thus also leaves the authentication for IE. Then, IE performs the authentication based on the "User Authentication" / "Logon" settings for the zone the Agent Tab is in. The default setting for the "Local Intranet Zone" in IE is to automatically try loggin on with the current user's credentials – i.e. AgentB's credentials in this case, because AgentB is the currently logged on (Windows) user. Only if authentication for this user fails, IE is going to prompt you for a different set of credentials. To change this behavior, you can set the security settings in IE accordingly:

Setting it to "Prompt for user name and password" will always prompt you for sites in the intranet zone. Once you've done that, exit OC and start it again. Now you should be prompted for the credentials to the Agent Tab and  you can provide AgentA's credentials. You then should see the correct list of groups AgentA is a member of.

Thursday, July 09, 2009 9:06 AM by roger

If you are like me working on server development you may run into the situation that a service fails early during startup, i.e. within the first couple of seconds. You'll soon realize that manually attaching a debugger doesn't work well, if at all. Even if you'll be running

windbg -pn MyService.exe
you may not actually be fast enough. Or there are multiple instances of that image running (e.g. SvcHost.exe) and the above command becomes kind of useless. Now if you have been reading the 'Debugging Tools for Windows' documentation (or have debugged services before) you'll already know what I am about to tell you here.

As I mentioned above, the first problem you're fighting with is that the failure happens very early. Another problem you may have is that your service is running e.g. as 'NT AUTHORITY\NETWORK SERVICE' and thus may not be able to interact with your desktop. But Windows' right here to help you out with 'Image File Execution Options'. This basically allows you to execute a command when a particular image is being executed.

You start by creating a key for the image file in the registry:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\MyService.exe
There you create a new string value of the name 'Debugger' and set its value to the path of the debugger you want to invoke, e.g. something like 'C:\Debuggers\cdb.exe'. To mitigate the problem of the non-interactive service, you'll probably want to use the debugger remotely, so you'll create a debugging server: 'C:\Debuggers\cdb.exe -server npipe:pipe=MyDebuggingSession'. What you have now is a debugger which is attached as soon as the service starts and which is accessible remotely. Thus, you can either on the server itself or another machine which has access run for instance

windbg -remote npipe:server=my-machine,pipe=MyDebuggingSession
and there you go: you are now debugging the service. Use the various command line options for the debuggers to

  • ignore the initial break point (-g)
  • run commands right after the debugger is attached (-c)
  • create a script which sets some useful breakpoints and run it when the debugger is attached (e.g. -c "$<MyScript.txt")

You may actually have to adjust the service control timeout. To do so, add a DWORD called 'ServicesPipeTimeout' to the registry key

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control
and set its value to the number of milliseconds you want the service to wait before timing out.

You'll find pretty much all of this information also in the help file for 'Debugging Tools for Windows' under 'Debugging a Service Application'. Enjoy!

 
About
Hi, my name is Roger Keller and I am a software engineer from Zürich, Switzerland living in Redmond, WA. I work in the Windows Services and Content group which is part of the Windows client team in Microsoft.