PowerShell and BizTalk RFID (1) - Providers and Server Configuration

PowerShell and BizTalk RFID (1) - Providers and Server Configuration

  • Comments 1

Been on the road pretty much non-stop since January, so I haven't had the opportunity to post as much as I'd like.  However, it looks like the next couple of months will be a little more reasonable in terms of the travel schedule, so I should be able to clear out a bit of the backlog of strange and interesting posts about RFID, BizTalk Server, and other bits of Microsoft technology.

I've always been a scripting guy, going back almost 15 years to being a pretty hard core UNIX sysadmin.  Cut my teeth on perl4, and every manner of shell scripting under the sun (as well as some that should never have seen the light of day).  Although I no longer have much (any) claim to vi wizardry (I'll keep my Visual Studio thank you very much) one of the aspects of the *NIX environment that I'd always missed was a rich scripting and automation interface.  With the release of PowerShell, the Windows platform has an amazing scripting environment that combines much of the magic of other scripting hosts, as well as some truly unique features leveraging the .NET platform.

If you've never been exposed to PowerShell before, I highly recommend checking out the following:

When deploying and managing (troubleshooting!) BizTalk RFID installations, there is often the need to automate certain tasks.  BizTalk RFID ships with a command line utility (rfidclientconsole.exe), but this isn't well suited to adhoc tasks and quick scripting, as it relies heavily on the use of parameter passing via complicated XML files.  Happily, PowerShell is very well suited to wrapping up .NET API's and making them consumable with rich parameters and aggregation.

The intent is to publish a series of blog posts on using PowerShell and RFID, and gradually build up a library of cmdlets that can be used to administer BizTalk RFID installations.

Source Code Package here.

Providers and Server Configuration Cmdlets

In this first post we'll pick off some low-hanging fruit - the ability to get information about providers and server configuration, then create some code to perform a basic sysadmin task - changing the log level of a provider or process component programatically.  Following the usual PowerShell naming conventions, we will create the following cmdlets (mimicking functionality of the rfidclientconsole application, only with a rich parameter experience):

Cmdlet Name Rfid Client Console equivalent Description
Get-RfidProviderStatus GetAllProviderStatus
Retrieve the list of registered providers and their current status.
Get-RfidProviderProperties GetProviderMetadata
Retrieve the list of properties for a given set of providers. 

Note that unlike the rfidclientconsole version, this will return all properties (by default the GetProviderProperties() API call only returns non-default values).
Get-RfidProviderMetadata GetProviderMetadata Retrieve the set of provider metadata for a given set of providers (metadata describes the available properties of the provider).
Get-RfidServerConfig GetServerConfiguration Retrieve the current server configuration.  This includes both the bootstrap (i.e. startup / initialization) and runtime parameters.

Note that the logging level for a component is an aspect of the server configuration - not of the component.
Set-RfidLoggingLevel NONE Set the logging level for a given component(s).


Creating PowerShell Cmdlets

To get started on creating PowerShell cmdlets, read the following excellent blog posts:

The samples in the rest of this post will use the PowerShell templates and debugging techniques from these posts.  To get started:

  1. Install David's project wizard from http://channel9.msdn.com/Photos/ZippedFiles/256835_PSTemplates.zip
  2. Create a new Windows PowerShell project called BizTalkRFID.PowerShell.  Make sure that the project is set to use .NET 3.5.
  3. Configure the project for auto-registering and debugging the cmdlet as per Mike Stall's post.

NOTE: If you are running on a 64-bit system, you need to GAC the output assembly with both the 32-bit and 64-bit versions of installutil.exe.  Do that with a post-build event command lineof:

c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\installutil.exe $(TargetDir)$(TargetFileName) 
c:\WINDOWS\Microsoft.NET\Framework64\v2.0.50727\installutil.exe $(TargetDir)$(TargetFileName)


We'll start with a fairly simple cmdlet - retrieving the status of a set of providers, leveraging the GetProviderStatus() API call.  The cmdlet should be able to take in the name (or names of a set) of a provider as well as a target server.  The signature of the cmdlet will be:

Get-RfidProviderStatus [Provider Names] [-Server servername]

1. Start off by adding a new Cmdlet to the project, under Add, New Item, Windows PowerShell, Windows PowerShell Cmdlet.  Name it GetRfidProviderStatusCmdlet.cs.

2. Add the following references to the project, from the C:\Program Files\Microsoft Biztalk RFID\bin\ directory.


3. Add the following using statements to the top of the class file:

using Microsoft.SensorServices.Rfid.Runtime;
using Microsoft.SensorServices.Rfid.Management;

4. Change the class Cmdlet attribute to reflect the desired name of the cmdlet.  Note that since this cmdlet does not have any impact on the system (i.e. doesn't change anything), we can set the SupportsShouldProcess value to false.

[Cmdlet(VerbsCommon.Get, "RfidProviderStatus", SupportsShouldProcess = false)]
    public class GetRfidProviderCmdlet : Cmdlet

4. Define the parameters, as per the following code.  This will define two parameters, ProviderName (an array of strings to represent the provider name), and Server (a single string to represent the target BizTalk RFID server).

   1:  [Parameter(Position = 0,
   2:      Mandatory = false,
   3:      ValueFromPipelineByPropertyName = true,
   4:      HelpMessage = "The name (or names) of the target providers")]
   5:  [ValidateNotNullOrEmpty]
   6:  [Alias("name")]
   7:  public string[] ProviderName
   8:  {
   9:      get;
  10:      set; 
  11:  }
  13:  [Parameter(Position = 1,
  14:      Mandatory = false,
  15:      ValueFromPipelineByPropertyName = true,
  16:      HelpMessage = "The name (or names) of the target BizTalk RFID server")]
  17:  [ValidateNotNullOrEmpty]
  18:  public string Server
  19:  {
  20:      get;
  21:      set;
  22:  }

5. Override the BeginProcessing method to create initialization code which will create the ProviderManagerProxy object pointing to the appropriate server.

   1:  private ProviderManagerProxy provProxy;
   3:  protected override void BeginProcessing()
   4:  {
   5:      if (String.IsNullOrEmpty(this.Server))
   6:          provProxy = new ProviderManagerProxy();
   7:      else
   8:          provProxy = new ProviderManagerProxy(this.Server);
   9:  }

6. Now the meat of the whole cmdlet development exercise - actually reaching out to the BizTalk RFID server and retrieving the provider status information.  Create an override for the ProcessRecord method, as per:

   1:  protected override void ProcessRecord()
   2:  {
   3:      try
   4:      {
   5:          if (ProviderName == null || ProviderName.Length == 0)
   6:              WriteVerbose("Retrieving status for all providers");
   7:          else
   8:              WriteVerbose("Retrieving status for providers " + String.Join(",", ProviderName));
  10:          ProviderStatus[] provStatus = provProxy.GetProviderStatus(ProviderName);
  11:          WriteObject(provStatus, true);
  12:      }
  13:      catch (RfidClientException ex0)
  14:      {
  15:          ApplicationException ex_app = new ApplicationException(ex0.RemoteErrorMessage, ex0);
  16:          WriteError(new ErrorRecord(ex_app, ex0.RemoteErrorCode, ErrorCategory.InvalidOperation, ProviderName));
  17:      }
  18:      catch (Exception ex1)
  19:      {
  20:          ThrowTerminatingError(new ErrorRecord(ex1, "", ErrorCategory.InvalidResult, ProviderName));
  21:      }
  22:  }        

This entire method boils down to two lines (10 and 11) - the rest is diagnostics and error handling.  Note that we have to catch the RfidClientException and wrap the RemoteErrorMessage, otherwise the only feedback to the user is "Remote method failed" - an instance of absolutely correct and singularly useless feedback.

7. Now that the cmdlet has been created (sans documentation - we'll get to that in a minute), it's time to see the fruits of our labours.  This cmdlet is executed against a base install of BizTalk RFID 2009 with the LLRP provider registered.  Press F5 to start a debuggable instance of PowerShell, and dump out the basic provider information:

PS C:\files\BizTalkRFID.PowerShell\bin\Debug> Get-RfidProviderStatus

Name           : LLRP
ProviderFaults : {}
LastFaultType  : None
LastFaultTime  : 1/1/0001 12:00:00 AM
ProviderState  : Registered

Exciting stuff, eh? :).  The real "wow" factor here isn't being able to dump out this basic list - could do that with rfidclientconsole.  The exciting part is now this information can leverage all of the power and utility of PowerShell scripting to do things, build reports, drive automation, etc.  Expanding the idea a bit:

PS C:\Files> Get-RfidProviderStatus | format-table Name,ProviderState -autosize

Name ProviderState
---- -------------
LLRP    Registered

Creating a simple table of available and registered providers.

   1:  PS C:\Files> Get-RfidProviderStatus | where-object { $_.ProviderState -eq 'Regis
   2:  tered' }
   5:  Name           : LLRP
   6:  ProviderFaults : {}
   7:  LastFaultType  : None
   8:  LastFaultTime  : 1/1/0001 12:00:00 AM
   9:  ProviderState  : Registered

Now we're starting to do fun things - with this command we filtered out registered provider names.  Further on in this series when we create cmdlets such as Start-RfidProvider, we could write simple scripts that automatically started all Registered providers with something as simple as

Get-RfidProviderStatus | ? {$_.ProviderState -eq 'Registered' } | Select-Object Name | Start-RfidProvider


The next cmdlet will be retrieving the metadata from a provider, or set of providers.  This cmdlet is nearly identical to the previous, the only difference being the cmdlet name, Cmdlet() attribute definition, and implementation of ProcessRecord.

   1:    [Cmdlet(VerbsCommon.Get, "RfidProviderMetadata", SupportsShouldProcess = false)]
   2:      public class MyCmdlet1 : Cmdlet
   3:      {
   4:  ...
   6:  protected override void ProcessRecord()
   7:  {
   8:      foreach (string name in ProviderName)
   9:      {                
  10:          try
  11:          {
  12:              WriteVerbose("Retrieving provider metadata..");
  13:              ProviderMetadata meta = provProxy.GetProviderMetadata(name);
  14:              WriteObject(meta);
  15:          }
  16:          catch (RfidClientException ex0)
  17:          {
  18:              ApplicationException ex_app = new ApplicationException(ex0.RemoteErrorMessage, ex0);
  19:              WriteError(new ErrorRecord(ex_app, ex0.RemoteErrorCode, ErrorCategory.InvalidOperation, name));
  20:          }
  21:          catch (Exception ex1)
  22:          {
  23:              ThrowTerminatingError(new ErrorRecord(ex1, "", ErrorCategory.InvalidResult, name));
  24:          }
  25:      }
  26:  }

Compile and run the updated project. From the PowerShell window you should now be able to execute the Get-RfidProviderMetadata cmdlet, as per:

ug> Get-RfidProviderMetadata LLRP -verbose
VERBOSE: Retrieving provider metadata..

ProviderInformation            : <providerInformation><id>Microsoft BizTalk RFI
                                 D LLRP Provider</id><description>Provider for
                                 LLRP devices</description><version></ve
ProviderCapabilities           : {The provider supports TCP/IP transport., The
                                 provider supports raising a defunct event., Th
                                 e provider supports device discovery., The pro
                                 vider supports triggering of device discovery.
ProviderPropertyMetadata       : {[Connection:Port], [General:LLRP Version], [M
                                 anagement:LLRP Message timeout], [Management:T
                                 CP KeepAlive Time]...}
VendorExtensionsEntityMetadata : {[HoppingEvent:DSPI management event], [Reader
                                 ExceptionEvent:DSPI management event], [RFSurv
                                 eyEvent:DSPI management event], [ConnectionAtt
                                 emptEvent:DSPI management event]...}
DevicePropertyMetadata         : {[LLRP General Capabilities:Antenna Sensitivit
                                 y Maximum Index], [LLRP General Capabilities:A
                                 ntenna Sensitivity Minimum Index], [LLRP Gener
                                 al Capabilities:Can Set Antenna Properties], [
                                 LLRP General Capabilities:Has UTC Clock Capabi


Not very exciting, either.  Or is it?  Remember that PowerShell uses the default formater defined by the underlying .NET type - in this case output as XML.  Those aren't text strings, but fully fledged objects that can be manipulated.  Like this:

PS C:\Files> $meta = Get-RfidProviderMetadata LLRP
PS C:\Files> $meta.ProviderCapabilities

Value              : 4
Description        : The provider supports TCP/IP transport.
IsDiscoveryRelated : False
IsEventRelated     : False
IsTransportRelated : True

Value              : 3
Description        : The provider supports raising a defunct event.
IsDiscoveryRelated : False
IsEventRelated     : True
IsTransportRelated : False

Value              : 1
Description        : The provider supports device discovery.
IsDiscoveryRelated : True
IsEventRelated     : False
IsTransportRelated : False

Value              : 2
Description        : The provider supports triggering of device discovery.
IsDiscoveryRelated : True
IsEventRelated     : False
IsTransportRelated : False

Displaying the list of capabilities for the LLRP provider.

PS C:\Files> $meta.DevicePropertyMetadata.Keys | sort GroupName

GroupName                               PropertyName
---------                               ------------
General                                 Regulatory region
General                                 Name
General                                 Firmware version
General                                 Vendor...

Listing out the provider properties (full list truncated for space reasons).

PS C:\Files> $meta.DevicePropertyMetadata.Keys | sort GroupName | format-table -autosize | select-object -first 10

GroupName                               PropertyName
---------                               ------------
General                                 Regulatory region
General                                 Name
General                                 Firmware version
General                                 Vendor
LLRP Access Report Spec                 Trigger
LLRP Antenna Configuration              Receiver Sensitivity Index
LLRP Antenna Configuration              Transmit Power Index
LLRP Antenna Configuration              Hop Table Id

Listing the first 10 provider properties, sorted by GroupName, with automatic sizing in the table formatting.


Now to dig a little deeper into provider properties.  The GetProviderProperties() method of the ProviderManagerProxy() returns the non-default (i.e. changed) values from the provider.  The GetProviderMetadata() returns the metadata for said properties, including the defaults.  Useful information, but in two separate buckets.  Let's write a cmdlet that returns a unified view of the properties using a custom PSObject (i.e. leveraging PowerShell's awesome adaptive type system).

1. This cmdlet is nearly identical to the previous, the only difference being the cmdlet name, Cmdlet() attribute definition, and implementation of ProcessRecord.  The definition of the class and Cmdlet attribute is:

   1:   [Cmdlet(VerbsCommon.Get, "RfidProviderProperty", SupportsShouldProcess = true)]
   2:      public class GetRfidProviderPropertiesCmdlet : Cmdlet
   3:      {

2. The implementation of ProcessRecord is a little trickier, and requires some explanation.

   1:  protected override void ProcessRecord()
   2:  {
   3:      foreach (string name in ProviderName)
   4:      {
   5:          try
   6:          {
   7:              WriteVerbose("Retrieving provider metadata..");
   8:              ProviderMetadata meta = provProxy.GetProviderMetadata(name);
  10:              WriteVerbose("Retrieving provider properties..");
  11:              PropertyProfile prof = provProxy.GetProperties(name);
  13:              foreach (PropertyKey k in meta.ProviderPropertyMetadata.Keys)
  14:              {
  15:                  PSObject obj = new PSObject(meta.ProviderPropertyMetadata[k]);
  16:                  obj.Properties.Add(new PSNoteProperty("Group", k.GroupName));
  17:                  obj.Properties.Add(new PSNoteProperty("Name", k.PropertyName));
  18:                  // Modified value
  19:                  if (prof.Keys.Contains(k))
  20:                  {
  21:                      PSNoteProperty n = new PSNoteProperty("Value", prof[k]);
  22:                      obj.Properties.Add(n);
  24:                      PSNoteProperty o = new PSNoteProperty("Default", false);
  25:                      obj.Properties.Add(o);
  26:                  }
  27:                  // Default value
  28:                  else
  29:                  {
  30:                      PSNoteProperty n = new PSNoteProperty("Value", 
  31:                          meta.ProviderPropertyMetadata[k].DefaultValue);
  32:                      obj.Properties.Add(n);
  34:                      PSNoteProperty o = new PSNoteProperty("Default", true);
  35:                      obj.Properties.Add(o);
  36:                  }                        
  37:                  WriteObject(obj);
  38:              }
  39:          }
  40:          catch (RfidClientException ex0)
  41:          {
  42:              ApplicationException ex_app = new ApplicationException(ex0.RemoteErrorMessage, ex0);
  43:              WriteError(new ErrorRecord(ex_app, ex0.RemoteErrorCode, ErrorCategory.InvalidOperation, name));
  44:          }
  45:          catch (Exception ex1)
  46:          {
  47:              ThrowTerminatingError(new ErrorRecord(ex1, "", ErrorCategory.InvalidResult, name));
  48:          }
  49:      }
  50:  }

Line 3 - Since we can receive multiple provider names, we'll iterate over each.

Line 7-11: In order to aggregate the property information, we need to retrieve both the metadata and property profile.

Line 13: As the metadata has the "master" list of all properties, we use this as the baseline for enumeration.

Line 15-17: Here is the PowerShell magic.  We're going to create a PowerShell object (PSObject) that wraps the native metadata object, and decorate it with some note properties.  In this case the GroupName and the property name.

Line 19-25: If the property profile contains the value, then it's a non-default value.  We add two note properties, one with the modified value, the other with a flag indicating its non-default status.

Line 30-35: If the property profile does not contain the value, then it's a default value, which we grab from the metadata.  We add two note properties, one with the modified value, the other with a flag indicating its default status.

Line 40-47: The usual error handling.

With this simple bit of code we can now do some interesting automation (well, for now we can play with data - once the Set-RfidProviderProperty cmdlets are written in future posts we can have more fun).

ug> Get-RfidProviderProperty LLRP | format-table

Group   Name      Value Default IsInitO Type    Descrip LowerRa HigherR ValueEx
                                    nly         tion        nge    ange pressio
-----   ----      ----- ------- ------- ----    ------- ------- ------- -------
Conn... Port       5084    True    True Syst... Prov...       1   65535
General LLRP...   1.0.1    True   False Syst... Indi... ...+308 ...+308
Mana... LLRP...   45000    True   False Syst... Time...   10000 ...3647
Mana... TCP ...   60000    True   False Syst... Time...   30000 ...3647
Conn... Devi...      60    True    True Syst... Inte...       1 ...3647
Disc... Matc...      10    True   False Syst... Indi...       1 ...3647

Retrieving all of the provider properties and their current values.

ug> Get-RfidProviderProperty Contoso | format-table Name,Value,Default -autosize

Name             Value                                  Default
----             -----                                  -------
Init_NonEditable Rfid                                      True
Name             Contoso                                   True
Description      Sample Microsoft BizTalk RFID Provider    True
DevelopedBy      Microsoft BizTalk RFID Team               True
Year Developed   2006                                      True
Integer_Editable 100                                       True
bool_Editable    True                                      True
DiscoveryPort    9876                                      True
HostDS           False                                     True
XML              False                                     True

Having a look at the Contoso provider's properties.

PS C:\files\projects\Microsoft.Rfid.PowerShell\Microsoft.Rfid.PowerShell\bin\Debug> Get-RfidProviderProperty Contoso | ? {$_.Type.FullName -eq 'System.String' } | format-table Name,Value -autosize

Name             Value
----             -----
Init_NonEditable Rfid
Name             Contoso
Description      Sample Microsoft BizTalk RFID Provider
DevelopedBy      Microsoft BizTalk RFID Team

Get a list of all of the string type properties.


Most of this article was written while trying to create this one cmdlet, for automating log level configuration.  This can be a somewhat non-intuitive exercise (or at least it was for me :), so a quick walkthrough of the steps involved may be useful.  The log level appears on the Initialization Parameters of the Properties page of a provider in the RFID Manager.  This may lead one to assume that the logging level is a property of the provider.  One would be flat out wrong :)


The logging level is an aspect of the RFID Server Configuration.  Let's use the rfidclientconsole to dump the server configuration and have a look:

   1:  C:\>rfidclientconsole GetServerConfiguration test.xml
   2:  C:\>type test.xml
   4:  <?xml version="1.0" encoding="utf-16"?>
   5:  <RfidServerConfiguration xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schemas.datacontract.org/2004/07/Microsoft.SensorServices.Rfid.Management">
   6:      <clusterNetworkName i:nil="true" />
   7:      <rfidServerBootstrapConfiguration>
   8:          <eventLoggingPolicyForExceptions>
   9:              <logNonRfidServerExceptions>true</logNonRfidServerExceptions>
  10:              <minimumExceptionSeverityForLogging>Fatal</minimumExceptionSeverityForLogging>
  11:          </eventLoggingPolicyForExceptions>
  12:          <log>
  13:              <component>
  14:                  <ComponentLogLevelType>
  15:                      <level>Info</level>
  16:                      <name>MSBizTalkRFID</name>
  17:                  </ComponentLogLevelType>
  18:                  <ComponentLogLevelType>
  19:                      <level>Info</level>
  20:                      <name>ProcessManager</name>
  21:                  </ComponentLogLevelType>
  22:                  <ComponentLogLevelType>
  23:                      <level>Info</level>
  24:                      <name>DeviceManager</name>
  25:                  </ComponentLogLevelType>
  26:                  <ComponentLogLevelType>
  27:                      <level>Info</level>
  28:                      <name>Device</name>
  29:                  </ComponentLogLevelType>
  30:                  <ComponentLogLevelType>
  31:                      <level>Info</level>
  32:                      <name>FixedTimerQ</name>
  33:                  </ComponentLogLevelType>
  34:                  <ComponentLogLevelType>
  35:                      <level>Info</level>
  36:                      <name>Common</name>
  37:                  </ComponentLogLevelType>
  38:                  <ComponentLogLevelType>
  39:                      <level>Info</level>
  40:                      <name>SecurityManager</name>
  41:                  </ComponentLogLevelType>
  42:                  <ComponentLogLevelType>
  43:                      <level>Info</level>
  44:                      <name>ProviderManager</name>
  45:                  </ComponentLogLevelType>
  46:                  <ComponentLogLevelType>
  47:                      <level>Info</level>
  48:                      <name>ComponentManager</name>
  49:                  </ComponentLogLevelType>
  50:                  <ComponentLogLevelType>
  51:                      <level>Info</level>
  52:                      <name>ConnectorServices</name>
  53:                  </ComponentLogLevelType>
  54:                  <ComponentLogLevelType>
  55:                      <level>Info</level>
  56:                      <name>RfidWebService</name>
  57:                  </ComponentLogLevelType>
  58:                  <ComponentLogLevelType>
  59:                      <level>Error</level>
  60:                      <name>Tracking</name>
  61:                  </ComponentLogLevelType>
  62:              </component>
  63:              <dateTimeFormat i:nil="true" />
  64:              <defaultLogLevel>Info</defaultLogLevel>
  65:              <logFile>logs\RfidServices.log</logFile>
  66:          </log>
  67:          <rfidStore>
  68:              <connectionString>Persist Security Info=False;database=RFIDSTORE;Integrated Security=SSPI; server=MASIMMS-DESKTOP</connectionString>
  69:          </rfidStore>
  70:          <wsConfiguration>
  71:              <wSPort>0</wSPort>
  72:              <wSPortSpecified>false</wSPortSpecified>
  73:          </wsConfiguration>
  74:      </rfidServerBootstrapConfiguration>
  75:      <rfidServerRuntimeConfiguration>
  76:          <deviceManagerConfiguration>
  77:              <autoApplyNonPersistentProperties>true</autoApplyNonPersistentProperties>
  78:              <deviceCommandResponseTimeoutMilliseconds>180000</deviceCommandResponseTimeoutMilliseconds>
  79:              <deviceConnectionCheckTimeMilliseconds>60000</deviceConnectionCheckTimeMilliseconds>
  80:              <deviceConnectionFailedThreshold>10</deviceConnectionFailedThreshold>
  81:              <deviceConnectionRetryTimeMilliseconds>60000</deviceConnectionRetryTimeMilliseconds>
  82:              <maxTagPrintedEvent>50</maxTagPrintedEvent>
  83:              <stampNotificationEventWithID>false</stampNotificationEventWithID>
  84:          </deviceManagerConfiguration>
  85:          <exposeStackTrace>false</exposeStackTrace>
  86:          <hostingConfiguration>
  87:              <iisHost>localhost</iisHost>
  88:              <iisPort>80</iisPort>
  89:              <iisTimeOutSeconds>60</iisTimeOutSeconds>
  90:              <iisWebsiteId>1</iisWebsiteId>
  91:          </hostingConfiguration>
  92:          <logConfiguration>
  93:              <currentBackupIndex>0</currentBackupIndex>
  94:              <fileCheckFrequency>25</fileCheckFrequency>
  95:              <fileCount>2</fileCount>
  96:              <fileSize>10</fileSize>
  97:              <maxAllowedDuplicates>3</maxAllowedDuplicates>
  98:              <spamControlTimePeriodInMinutes>10</spamControlTimePeriodInMinutes>
  99:          </logConfiguration>
 100:          <processManagerConfiguration />
 101:          <providerManagerConfiguration>
 102:              <delegateThreadTimeoutMilliseconds>60000</delegateThreadTimeoutMilliseconds>
 103:              <providerSideBySideLoadAllowed>false</providerSideBySideLoadAllowed>
 104:          </providerManagerConfiguration>
 105:          <wSConfiguration>
 106:              <shouldAuditSuccess>false</shouldAuditSuccess>
 107:          </wSConfiguration>
 108:      </rfidServerRuntimeConfiguration>
 109:  </RfidServerConfiguration>

Wow.  That's a lot of stuff.  The interesting items are the logging components.  These define the individual logging level for each component in the system, both internal system components and the "user" components such as providers and processes. 

Note that the LLRP provider does NOT appear in this list.  This is due to the INHERIT LOG LEVEL checkbox being set in the RFID Manager.  If a user component is set to inherit a logging setting, it will not have an entry in the RFID Server Configuration data set.  This comes into play in our implementation of the Set-RfidLoggingLevel command.

The ServerManagerProxy exposes a very chunky interface, meaning that we have to retrieve the RFID server configuration, make changes to that configuration, then dump the configuration back in order to effect any changes.

1. This cmdlet is nearly identical to the previous, the only difference being the cmdlet name, Cmdlet() attribute definition, and implementation of ProcessRecord.  The definition of the class and Cmdlet attribute is:

   1:   [Cmdlet(VerbsCommon.Set, "RfidLogLevel", SupportsShouldProcess = true)]
   2:      public class SetRfidLogLevelCmdlet : Cmdlet
   3:      {

2. The parameter set is a little different, consisting of the Log Level, followed by the component names with an optional Server parameter.

   1:  [Parameter(Position = 0,
   2:      Mandatory = true,
   3:      ValueFromPipelineByPropertyName = true,
   4:      HelpMessage = "Logging Level")]
   5:  [ValidateNotNullOrEmpty]
   6:  public ConfigLogLevel Level
   7:  {
   8:      get;
   9:      set; 
  10:  }
  12:  [Parameter(Position = 1,
  13:  Mandatory = true,
  14:  ValueFromPipelineByPropertyName = true,
  15:  HelpMessage = "The name (or names) of the target components (providers and processes")]
  16:  [ValidateNotNullOrEmpty]
  17:  [Alias("name")]
  18:  public string[] ComponentName
  19:  {
  20:      get;
  21:      set;
  22:  }
  24:  [Parameter(Position = 2,
  25:      Mandatory = false,
  26:      ValueFromPipelineByPropertyName = true,
  27:      HelpMessage = "The name of the target BizTalk RFID server")]
  28:  [ValidateNotNullOrEmpty]
  29:  public string Server
  30:  {
  31:      get;
  32:      set;
  33:  }

3. The initialization code is also a little different.  In order to perform parameter validation we need to have the list of user components (providers and processes), and will thus need the proxy objects to access these in addition to the ServerManagerProxy.

   1:  private ServerManagerProxy srvProxy;
   2:  private ProviderManagerProxy provProxy;
   3:  private ProcessManagerProxy procProxy;
   5:  protected override void BeginProcessing()
   6:  {
   7:      if (String.IsNullOrEmpty(this.Server))
   8:      {
   9:          srvProxy = new ServerManagerProxy();
  10:          provProxy = new ProviderManagerProxy();
  11:          procProxy = new ProcessManagerProxy();
  12:      }
  13:      else
  14:      {
  15:          srvProxy = new ServerManagerProxy(this.Server);
  16:          provProxy = new ProviderManagerProxy(this.Server);
  17:          procProxy = new ProcessManagerProxy(this.Server);
  18:      }
  19:  }

4. Now the good stuff.  Actually implementing the changes. 

   1:  protected override void ProcessRecord()
   2:  {
   3:      try
   4:      {
   5:          // Get the server configuration, which contains the logging level information in the
   6:          // bootstrap (i.e. startup) settings
   7:          WriteVerbose("Retrieving server configuration");
   8:          RfidServerConfiguration config = srvProxy.GetServerConfiguration();
  10:          // Get the list of provider and process names - these are the only valid components
  11:          WriteVerbose("Retrieving provider list");
  12:          ProviderStatus[] provStatus = provProxy.GetProviderStatus(null);
  13:          List<string> providerNames = new List<string>();
  14:          foreach (ProviderStatus s in provStatus)
  15:              providerNames.Add(s.Name);
  17:          WriteVerbose("Retrieving process list");
  18:          List<string> procNames = new List<string>(procProxy.GetAllProcesses());
  20:          // Perform parameter validation on the list of input components
  21:          List<string> validComponents = new List<string>();
  22:          foreach (string name in this.ComponentName)
  23:          {
  24:              if (procNames.Contains(name) || providerNames.Contains(name))
  25:              {
  26:                  WriteVerbose("Component " + name + " is a valid logging component");
  27:                  validComponents.Add(name);
  28:              }
  29:              else
  30:              {
  31:                  WriteWarning("Component " + name + " is not a process or provider name");
  32:              }
  33:          }
  35:          foreach (ComponentLogLevelType n in config.RfidServerBootstrapConfiguration.log.component)
  36:          {
  37:              if (validComponents.Contains(n.name))
  38:              {
  39:                  WriteVerbose("Changing log level of component " + n.name + " to " + Level.ToString());
  40:                  n.level = this.Level;
  41:                  validComponents.Remove(n.name);
  42:              }
  43:          }
  45:          // Check for "default" logging levels
  46:          List<ComponentLogLevelType> vals = new List<ComponentLogLevelType>(
  47:                  config.RfidServerBootstrapConfiguration.log.component);
  49:          foreach (string name in validComponents)
  50:          {
  51:              WriteVerbose("Setting log level of component " + name + " to non-default value " + Level.ToString());
  52:              ComponentLogLevelType t = new ComponentLogLevelType();
  53:              t.name = name;
  54:              t.level = Level;
  55:              vals.Add(t);
  56:          }
  57:          config.RfidServerBootstrapConfiguration.log.component = vals.ToArray();
  59:          WriteVerbose("Updating Server..");
  60:          srvProxy.SetServerConfiguration(config);
  62:      }
  63:      catch (RfidClientException ex0)
  64:      {
  65:          ApplicationException ex_app = new ApplicationException(ex0.RemoteErrorMessage, ex0);
  66:          ThrowTerminatingError(new ErrorRecord(ex_app, ex0.RemoteErrorCode, ErrorCategory.InvalidOperation, this.Server));
  67:      }
  68:  }
  69:  }



Lines Description
7-8 Retrieving the RFID Server Configuration, which contains the logging components.  See the XML dump above for the typical contents.
11-18 Retrieving the list of process and provider names, and making them available in List<> objects.
21-33 Filtering out any non-valid component names.
35-43 Non-inheriting log levels will be present in the server configuration.  Find the log level object for the component, and modify the log level.
46-57 Since inherited logging level don't have a record in the component list, we need to add any new values to a list, and then replace the list stored in the server configuration.
59-60 Updating the server with the new configuration.

All of this code (well, it's not a huge amount), but we get a very straightforward logging configuration experience:

ug> Set-RfidLogLevel Verbose LLRP -verbose
VERBOSE: Retrieving server configuration
VERBOSE: Retrieving provider list
VERBOSE: Retrieving process list
VERBOSE: Component LLRP is a valid logging component
VERBOSE: Changing log level of component LLRP to Verbose
VERBOSE: Updating Server..

Once we have the opportunity to implement more of the Get-Rfid* cmdlets, this method can be used to do some interesting things.  However, since we already have a way to retrieve a list of the provider names:

ug> Get-RfidProviderStatus | select-object Name | Set-RfidLogLevel Verbose -verbose
VERBOSE: Retrieving server configuration
VERBOSE: Retrieving provider list
VERBOSE: Retrieving process list
VERBOSE: Component LLRP is a valid logging component
VERBOSE: Changing log level of component LLRP to Verbose
VERBOSE: Updating Server..
VERBOSE: Retrieving server configuration
VERBOSE: Retrieving provider list
VERBOSE: Retrieving process list
VERBOSE: Component Contoso is a valid logging component
VERBOSE: Changing log level of component Contoso to Verbose
VERBOSE: Updating Server..

PowerShell script to set all of the Provider log levels to Verbose.

Note: because of the use of ProcessRecord the log levels are being changed sequentially.  A more efficient way of doing it would be to aggregate the component names in ProcessRecord and actually update the server in EndProcessing.

Leave a Comment
  • Please add 5 and 7 and type the answer here:
  • Post