SDK: In-depth sample on how to use the client messaging SDK

Update: I've updated this sample with ISV proxy registration capabilities.

At the Microsoft 2012 Partner Day in Las Vegas there was a session previewing changes to the Configuration Manager 2012 SDK. There was a demo given with a sample program that was using the client messaging SDK to register a client, send discovery data, inventory, and file collections. These were then demonstrated in the console showing an end to end scenario. This was all done in seconds showing how fast everything can go.

I am now sharing that sample program to show a practical example on how you can use the client messaging SDK to register a client and send data up to the site. This sample will demonstrate various functionality of the client messaging SDK.

Here is the code used in the sample (there is a link at the end of this post to download the .cs file so you don’t have to worry about copy/paste format issues):

  1: using System;
  2: using System.Diagnostics;
  3: using System.Diagnostics.CodeAnalysis;
  4: using System.Globalization;
  5: using System.IO;
  6: using Microsoft.ConfigurationManagement.Messaging.Framework;
  7: using Microsoft.ConfigurationManagement.Messaging.Messages;
  8: using Microsoft.ConfigurationManagement.Messaging.Sender.Http;
  9:  
  10: namespace Microsoft.ConfigurationManagement.Messaging.Tools.InteractiveSdkSample
  11: {
  12:     internal class SampleProgram
  13:     {
  14:         private static readonly HttpSender Sender = new HttpSender();
  15:         private static readonly ConsoleTraceListener TraceListener = new ConsoleTraceListener();
  16:         private static MessageCertificateX509 certificate;
  17:         private static SmsClientId clientId;
  18:         private static bool compression;
  19:         private static bool encryption;
  20:  
  21:         private static string mpHostname;
  22:         private static bool replyCompression;
  23:         private static string siteCode;
  24:         private static bool verbose;
  25:  
  26:         /// <summary>
  27:         /// Sets the logging parameters
  28:         /// </summary>
  29:         private static void ChangeLogging()
  30:         {
  31:             if (true == verbose)
  32:             {
  33:                 MessagingTrace.TraceSwitch.Level = TraceLevel.Info;
  34:             }
  35:             else
  36:             {
  37:                 MessagingTrace.TraceSwitch.Level = TraceLevel.Warning;
  38:             }
  39:         }
  40:  
  41:         /// <summary>
  42:         /// Writes help to the console and exits
  43:         /// </summary>
  44:         private static void Help()
  45:         {
  46:             Console.WriteLine("{0} <MP Hostname> <SiteCode> [-v]", Environment.GetCommandLineArgs()[0]);
  47:             Console.WriteLine("-v: verbose");
  48:             Environment.Exit(1);
  49:         }
  50:  
  51:         /// <summary>
  52:         /// Performs initialization, should only be run once
  53:         /// </summary>
  54:         private static void Initialize()
  55:         {
  56:             // Only dump readable characters since we're writing to the console
  57:             HexDumper.OnlyPrintReadable = true;
  58:  
  59:             // Initialize the sender and event handlers
  60:             Sender.OnSend += OnSend;
  61:             Sender.OnReceived += OnReceived;
  62:  
  63:             // There must be a certificate file that matches the path and password.
  64:             certificate = new MessageCertificateX509Volatile(Path.Combine(Environment.CurrentDirectory, "MixedModeTestCert.pfx"), "sccm");
  65:  
  66:             if (certificate == null)
  67:             {
  68:                 Console.WriteLine(@"Could not load the test certificate");
  69:                 Environment.Exit(1);
  70:             }
  71:  
  72:             Console.WriteLine(@"Using certificate for client authentication with thumbprint of '{0}'", certificate.Thumbprint);
  73:  
  74:             Trace.Listeners.Add(TraceListener);
  75:             ChangeLogging();
  76:         }
  77:  
  78:         /// <summary>
  79:         /// Input loop that runs when a specific step isn't specified at the command line
  80:         /// </summary>
  81:         [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
  82:         private static void InputLoop()
  83:         {
  84:             while (true)
  85:             {
  86:                 WriteSteps();
  87:                 Console.WriteLine(@"V -- change verbosity level");
  88:                 Console.WriteLine(@"Q -- quit");
  89:                 Console.WriteLine(@"C -- compression enable/disable");
  90:                 Console.WriteLine(@"R -- reply compression enable/disable");
  91:                 Console.WriteLine(@"E -- encryption enable/disable");
  92:                 Console.Write(@"Input: ");
  93:                 ConsoleKeyInfo key = Console.ReadKey();
  94:                 Console.WriteLine();
  95:  
  96:                 switch (key.Key)
  97:                 {
  98:                     case ConsoleKey.R:
  99:                         replyCompression = !replyCompression;
  100:                         Console.WriteLine(@"Setting reply compression enabled to: {0}", replyCompression);
  101:                         break;
  102:                     case ConsoleKey.E:
  103:                         encryption = !encryption;
  104:                         Console.WriteLine(@"Setting encryption enabled to: {0}", encryption);
  105:                         break;
  106:                     case ConsoleKey.C:
  107:                         compression = !compression;
  108:                         Console.WriteLine(@"Setting compression enabled to: {0}", compression);
  109:                         break;
  110:                     case ConsoleKey.Q:
  111:                         Console.WriteLine(@"Quitting");
  112:                         Environment.Exit(0);
  113:                         break;
  114:                     case ConsoleKey.D1:
  115:                         PerformOperation(Step.Register);
  116:                         break;
  117:                     case ConsoleKey.D2:
  118:                         PerformOperation(Step.Ddr);
  119:                         break;
  120:                     case ConsoleKey.D3:
  121:                         PerformOperation(Step.Hinv);
  122:                         break;
  123:                     case ConsoleKey.D4:
  124:                         PerformOperation(Step.Sinv);
  125:                         break;
  126:                     case ConsoleKey.D5:
  127:                         PerformOperation(Step.FileCollection);
  128:                         break;
  129:                     case ConsoleKey.D6:
  130:                         PerformOperation(Step.MachinePolicy);
  131:                         break;
  132:                     case ConsoleKey.D7:
  133:                         PerformOperation(Step.UserPolicy);
  134:                         break;
  135:                     default:
  136:                         Console.WriteLine(@"Invalid input received: " + key.KeyChar);
  137:                         break;
  138:                 }
  139:             }
  140:         }
  141:  
  142:         /// <summary>
  143:         /// Main program section
  144:         /// </summary>
  145:         private static void Main(string[] args)
  146:         {
  147:             if (args.Length < 2)
  148:             {
  149:                 Help();
  150:             }
  151:  
  152:             mpHostname = args[0];
  153:             siteCode = args[1];
  154:  
  155:             if (siteCode.Length != 3)
  156:             {
  157:                 Console.WriteLine(@"Invalid site code {0} specified", siteCode);
  158:                 Help();
  159:             }
  160:  
  161:             if (args.Length == 3)
  162:             {
  163:                 if (args[2] == "-v")
  164:                 {
  165:                     verbose = true;
  166:                 }
  167:                 else
  168:                 {
  169:                     Help();
  170:                 }
  171:             }
  172:             else if (args.Length != 2)
  173:             {
  174:                 Help();
  175:             }
  176:  
  177:             Initialize();
  178:             InputLoop();
  179:         }
  180:  
  181:         /// <summary>
  182:         /// Captures data received by HTTP sender and logs if verbose is enabled
  183:         /// </summary>
  184:         private static void OnReceived(object sender, MessageSenderEventArgs e)
  185:         {
  186:             if (verbose == false)
  187:             {
  188:                 return;
  189:             }
  190:  
  191:             Console.WriteLine(@"Received payload from MP:");
  192:             Console.WriteLine(e.Message.Body.Payload);
  193:         }
  194:  
  195:         /// <summary>
  196:         /// Captures data sent by HTTP sender and logs if verbose is enabled
  197:         /// </summary>
  198:         private static void OnSend(object sender, MessageSenderEventArgs e)
  199:         {
  200:             if (verbose == false)
  201:             {
  202:                 return;
  203:             }
  204:  
  205:             Console.WriteLine(@"Sending payload to MP:");
  206:             Console.WriteLine(e.Message.Body.Payload);
  207:  
  208:             if (e.Message.Attachments.Count > 0)
  209:             {
  210:                 if (e.Message is ConfigMgrFileCollectionMessage ||
  211:                     e.Message is ConfigMgrUploadRequest)
  212:                 {
  213:                     Console.WriteLine(@"Not writing attachments for {0} message to console", e.Message);
  214:                     return;
  215:                 }
  216:  
  217:                 for (int i = 0; i < e.Message.Attachments.Count; i++)
  218:                 {
  219:                     Console.WriteLine(@"+======== ATTACHMENT #{0} (name: {1})========+", i + 1, e.Message.Attachments[i].Name);
  220:                     Console.WriteLine(e.Message.Attachments[i].Body.Payload);
  221:                 }
  222:             }
  223:         }
  224:  
  225:         /// <summary>
  226:         /// Performs an atomic operation
  227:         /// </summary>
  228:         [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling")]
  229:         [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes")]
  230:         private static void PerformOperation(Step step)
  231:         {
  232:             // Some steps require a valid client ID to work, this anonymous method ensures this.
  233:             Action checkIsClientIdPresent = () =>
  234:                                                 {
  235:                                                     if (null == clientId)
  236:                                                     {
  237:                                                         throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Cannot execute step #{0:d} {0} because client is not registered.", step));
  238:                                                     }
  239:                                                 };
  240:  
  241:             try
  242:             {
  243:                 Console.WriteLine(@"===========================================================");
  244:                 Console.WriteLine(@"Sending '{0}' message", step);
  245:                 Console.WriteLine(@"===========================================================");
  246:                 switch (step)
  247:                 {
  248:                     case Step.Register:
  249:  
  250:                         ConfigMgrRegistrationRequest regRequest = new ConfigMgrRegistrationRequest();
  251:                         regRequest.Settings.HostName = mpHostname;
  252:                         regRequest.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  253:  
  254:                         // Manual registration
  255:                         // Required: must supply a NetBIOS hostname
  256:                         regRequest.NetBiosName = "MyDummyHostName";
  257:  
  258:                         // Required: must supply an agent identity (this is the name of your client)
  259:                         regRequest.AgentIdentity = "MyCustomClient.exe";
  260:  
  261:                         // Required: must supply a client FQDN
  262:                         regRequest.ClientFqdn = "MyDummyHostName.MyDomain.net";
  263:  
  264:                         // You can try enabling this to authenticate your client at registration. This may prevent having to "Approve" the client in the administrator
  265:                         // console.
  266:                         //regRequest.Settings.Security.AuthenticationType = AuthenticationType.WindowsAuth;
  267:  
  268:                         regRequest.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  269:                         regRequest.Settings.ReplyCompression = (true == replyCompression) ? MessageCompression.Zlib : MessageCompression.None;
  270:                         clientId = regRequest.RegisterClient(Sender, TimeSpan.FromMinutes(5));
  271:                         Console.WriteLine(@"Got SMSID from registration of: {0}", clientId);
  272:                         break;
  273:  
  274:                     case Step.Ddr:
  275:                         checkIsClientIdPresent();
  276:  
  277:                         ConfigMgrDataDiscoveryRecordMessage ddrMessage = new ConfigMgrDataDiscoveryRecordMessage();
  278:                         ddrMessage.Settings.HostName = mpHostname;
  279:                         ddrMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  280:                         ddrMessage.SmsId = clientId;
  281:                         ddrMessage.SiteCode = siteCode;
  282:                         ddrMessage.ADSiteName = "MyADSiteName";
  283:                         ddrMessage.DomainName = "MyDomain.net";
  284:                         ddrMessage.NetBiosName = "MyDummyHostName";
  285:                         ddrMessage.ClientVersion = new ClientVersionV5();
  286:                         ddrMessage.Discover();
  287:                         ddrMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  288:                         ddrMessage.Settings.Security.EncryptMessage = encryption;
  289:                         ddrMessage.SendMessage(Sender);
  290:                         break;
  291:  
  292:                     case Step.Hinv:
  293:                         checkIsClientIdPresent();
  294:  
  295:                         ConfigMgrHardwareInventoryMessage hinvMessage = new ConfigMgrHardwareInventoryMessage();
  296:                         hinvMessage.Settings.HostName = mpHostname;
  297:                         hinvMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  298:                         hinvMessage.SmsId = clientId;
  299:                         hinvMessage.SiteCode = siteCode;
  300:                         hinvMessage.NetBiosName = "MyDummyHostName";
  301:                         hinvMessage.DomainName = "MyDomain.net";
  302:                         hinvMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  303:                         hinvMessage.Settings.Security.EncryptMessage = encryption;
  304:                         hinvMessage.AddInstancesToInventory(WmiClassToInventoryReportInstance.WmiClassToInventoryInstances(@"root\cimv2", "Win32_LogicalDisk", @"root\cimv2\sms", "SMS_LogicalDisk"));
  305:                         hinvMessage.AddInstancesToInventory(WmiClassToInventoryReportInstance.WmiClassToInventoryInstances(@"root\cimv2", "Win32_Processor", @"root\cimv2\sms", "SMS_Processor"));
  306:                         hinvMessage.AddInstancesToInventory(WmiClassToInventoryReportInstance.WmiClassToInventoryInstances(@"root\cimv2", "Win32_SystemDevices", @"root\cimv2\sms", "SMS_SystemDevices"));
  307:                         hinvMessage.SendMessage(Sender);
  308:                         break;
  309:  
  310:                     case Step.Sinv:
  311:                         checkIsClientIdPresent();
  312:  
  313:                         ConfigMgrSoftwareInventoryMessage sinvMessage = new ConfigMgrSoftwareInventoryMessage();
  314:                         sinvMessage.Settings.HostName = mpHostname;
  315:                         sinvMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  316:                         sinvMessage.SmsId = clientId;
  317:                         sinvMessage.SiteCode = siteCode;
  318:                         sinvMessage.NetBiosName = "MyDummyHostName";
  319:                         sinvMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  320:                         sinvMessage.Settings.Security.EncryptMessage = encryption;
  321:                         sinvMessage.AddDirectoryFilesToInventory(Environment.ExpandEnvironmentVariables(@"%WINDIR%\system32"), "*.exe", true, true);
  322:                         InventoryInstanceElementFileSystemFile fakeFile = new InventoryInstanceElementFileSystemFile();
  323:                         fakeFile.LastWriteDate = TimeHelpers.Epoch.AddDays(-200);
  324:                         fakeFile.FileVersion = "1.0";
  325:                         fakeFile.FileDescription = "Fake File";
  326:                         fakeFile.CompanyName = "Fake Company";
  327:                         fakeFile.FileName = "FakeFile.EXE";
  328:                         fakeFile.FilePath = Environment.ExpandEnvironmentVariables(@"%SYSTEMDRIVE%\MyFakeFile.EXE");
  329:                         fakeFile.ProductName = "Fake Product";
  330:                         fakeFile.ProductVersion = "1.0";
  331:                         fakeFile.Size = 1024;
  332:                         sinvMessage.AddFileSystemFileInstanceToInventory(fakeFile);
  333:                         sinvMessage.SendMessage(Sender);
  334:                         break;
  335:  
  336:                     case Step.FileCollection:
  337:                         checkIsClientIdPresent();
  338:  
  339:                         ConfigMgrFileCollectionMessage fcMessage = new ConfigMgrFileCollectionMessage();
  340:                         fcMessage.Settings.HostName = mpHostname;
  341:                         fcMessage.Settings.BitsUpload = true;
  342:                         fcMessage.Discover();
  343:                         fcMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  344:                         fcMessage.SmsId = clientId;
  345:                         fcMessage.SiteCode = siteCode;
  346:                         fcMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  347:                         fcMessage.Settings.Security.EncryptMessage = encryption;
  348:                         fcMessage.AddDirectoryFilesToCollection(Environment.ExpandEnvironmentVariables(@"%WINDIR%\system32"), "*.log", true, true);
  349:                         fcMessage.SendMessage(Sender);
  350:                         break;
  351:  
  352:                     case Step.UserPolicy:
  353:                         checkIsClientIdPresent();
  354:                         ConfigMgrPolicyAssignmentRequest userPolicyMessage = new ConfigMgrPolicyAssignmentRequest();
  355:                         userPolicyMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  356:                         userPolicyMessage.Settings.HostName = mpHostname;
  357:                         userPolicyMessage.ResourceType = PolicyAssignmentResourceType.User;
  358:                         userPolicyMessage.Settings.Security.AuthenticationScheme = AuthenticationScheme.Ntlm;
  359:                         userPolicyMessage.Settings.Security.AuthenticationType = AuthenticationType.WindowsAuth;
  360:                         userPolicyMessage.SmsId = clientId;
  361:                         userPolicyMessage.SiteCode = siteCode;
  362:                         userPolicyMessage.Discover();
  363:                         userPolicyMessage.SendMessage(Sender);
  364:                         userPolicyMessage.Settings.Security.EncryptMessage = encryption;
  365:                         userPolicyMessage.Settings.ReplyCompression = (true == replyCompression) ? MessageCompression.Zlib : MessageCompression.None;
  366:                         userPolicyMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  367:                         break;
  368:  
  369:                     case Step.MachinePolicy:
  370:                         checkIsClientIdPresent();
  371:                         ConfigMgrPolicyAssignmentRequest machinePolicyMessage = new ConfigMgrPolicyAssignmentRequest();
  372:                         machinePolicyMessage.Settings.HostName = mpHostname;
  373:                         machinePolicyMessage.AddCertificateToMessage(certificate, CertificatePurposes.Signing | CertificatePurposes.Encryption);
  374:                         machinePolicyMessage.Settings.Security.EncryptMessage = encryption;
  375:                         machinePolicyMessage.Settings.Compression = (true == compression) ? MessageCompression.Zlib : MessageCompression.None;
  376:                         machinePolicyMessage.Settings.ReplyCompression = (true == replyCompression) ? MessageCompression.Zlib : MessageCompression.None;
  377:                         machinePolicyMessage.ResourceType = PolicyAssignmentResourceType.Machine;
  378:                         machinePolicyMessage.SmsId = clientId;
  379:                         machinePolicyMessage.SiteCode = siteCode;
  380:                         machinePolicyMessage.Discover();
  381:                         machinePolicyMessage.SendMessage(Sender);
  382:  
  383:                         break;
  384:                 }
  385:  
  386:                 Console.WriteLine(@"===========================================================");
  387:                 Console.WriteLine(@"Successfully sent '{0}' message!", step);
  388:                 Console.WriteLine(@"===========================================================");
  389:             }
  390:             catch (Exception e)
  391:             {
  392:                 e.RethrowCriticalException();
  393:                 Console.WriteLine();
  394:                 MessagingTrace.TraceException("Got unexpected exception while executing messaging step #{0:d}: '{0}'", e, step);
  395:                 Console.WriteLine(@"===========================================================");
  396:                 Console.WriteLine(@"Failed to send '{0}' message!", step);
  397:                 Console.WriteLine(@"===========================================================");
  398:             }
  399:         }
  400:  
  401:         /// <summary>
  402:         /// Writes the various steps that can be executed with this program to the console
  403:         /// </summary>
  404:         private static void WriteSteps()
  405:         {
  406:             Console.WriteLine(@"1 -- register client");
  407:             Console.WriteLine(@"2 -- send DDR");
  408:             Console.WriteLine(@"3 -- send hardware inventory");
  409:             Console.WriteLine(@"4 -- send software inventory");
  410:             Console.WriteLine(@"5 -- send collected files");
  411:             Console.WriteLine(@"6 -- request machine policy");
  412:             Console.WriteLine(@"7 -- request user policy");
  413:         }
  414:  
  415:         #region Nested type: Step
  416:  
  417:         /// <summary>
  418:         /// Steps supported by this sample
  419:         /// </summary>
  420:         private enum Step
  421:         {
  422:             /// <summary>
  423:             /// None (implementation implies manual selection loop)
  424:             /// </summary>
  425:             None = 0, 
  426:  
  427:             /// <summary>
  428:             /// Register client
  429:             /// </summary>
  430:             Register = 1, 
  431:  
  432:             /// <summary>
  433:             /// Send DDR
  434:             /// </summary>
  435:             Ddr = 2, 
  436:  
  437:             /// <summary>
  438:             /// Send HINV
  439:             /// </summary>
  440:             Hinv = 3, 
  441:  
  442:             /// <summary>
  443:             /// Send SINV
  444:             /// </summary>
  445:             Sinv = 4, 
  446:  
  447:             /// <summary>
  448:             /// Send collected files
  449:             /// </summary>
  450:             FileCollection = 5, 
  451:  
  452:             /// <summary>
  453:             /// Request machine policy
  454:             /// </summary>
  455:             MachinePolicy = 6, 
  456:  
  457:             /// <summary>
  458:             /// Request user policy
  459:             /// </summary>
  460:             UserPolicy = 7, 
  461:         }
  462:  
  463:         #endregion
  464:     }
  465: }

A couple of quick notes about the sample.

The sample as coded requires two arguments at the command line: the management point, and the site code. If this is the first time you’re running this on your site, you may need to “Approve” the client through the administrator console. You can also try to change the registration request to authenticate your machine by setting regRequest.Settings.Authentication

As coded, it expects a certificate file called MixedModeTestCert.pfx with a password of sccm to be in the same directory as the executable (see line 74). You can obviously change this to your liking but that’s how it’s implemented. To create this certificate, you can use makecert.exe (part of the Windows SDK):

makecert.exe -len 2048 -sky exchange -a sha1 -sy 24 -pe -r -n CN="ConfigMgr Test Certificate" -ss MY

That will create a certificate and add it to your Personal certificate store. You can then export it and provide a password that matches the code. Alternatively, you could change line 74 to use MessageCertificateX509File instead. The difference between MessageCertificateX509Volatile and MessageCertificateX509File is that the ‘Volatile class reads a certificate file into memory while the ‘File class reads from the certificate store (I admit the naming is a bit confusing).

I hope you found this sample useful.

Download the code sample: Program.cs