Not that long ago I was challenged with the task of writing a piece of automation for a ConfigMgr Client to check if a specific package was available in it’s boundary; or commonly referred to as a “Content Location Request”. All I was able to find was Robert Marshall’s [MVP] postings for Content Location Request (CLR) Generator (and the Console Edition). The hints at the ConfigMgr SDK we’re clear, so I went ahead and installed it.
To get a jump start I began looking for examples and yet again found a post by Robert Marshall.
The best part here is this works with SCCM 2012 AND the latest Technical Preview that Microsoft has released for Configuration Manager vNext!
So, here we go! Content Location Request’s using C# or Powershell!
With the ConfigMgr SDK installed we’ll be focusing on the Client Messaging SDK: C:\Program Files (x86)\Microsoft System Center 2012 R2 Configuration Manager SDK\Samples\Client Messaging SDK. Inside this folder you’ll see a Sample.cs file and help file (most important!). if you decide to go beyond what I do here, you’ll want to keep the help file close by! Navigating up and in the Redistributables folder you’ll find the Microsoft.ConfigurationManagement.Messaging.dll.
Loading Files
If using Visual Studio, add a reference to the Messaging dll and the appropriate using reference. While you’re at it add a using statement for System.Diagnostics if one does not already exist.
C#
using System.Diagnostics; using Microsoft.ConfigurationManagement.Messaging.Framework; using Microsoft.ConfigurationManagement.Messaging.Messages; using Microsoft.ConfigurationManagement.Messaging.Sender.Http;
Powershell
[void] [Reflection.Assembly]::LoadWithPartialName("System.Diagnostics") [void] [Reflection.Assembly]::LoadFile("c:\Microsoft.ConfigurationManagement.Messaging.dll")
When using the “LoadFile” method, you’ll need to put in the full path (no variables). In most cases this is an issue, especially if including the dll within the script path. Best way I’ve found is to us the Add-Type cmdlet (where a variable populated path is allowed) in a try block, then have a catch statement to copy the file and then call LoadFile. If using this in WinPE, you’ll NEED to do this due to the loadFromRemoteSouces default setting.
Powershell if anticipating WinPE:
$Script:CurrentDirectory = Split-Path -Parent $MyInvocation.MyCommand.Definition try { Add-Type -Path "$Script:CurrentDirectory\Utilities\Microsoft.ConfigurationManagement.Messaging.dll" | out-null } catch { Copy-Item -Path "$Script:CurrentDirectory\Utilities\Microsoft.ConfigurationManagement.Messaging.dll" -Destination "x:\Microsoft.ConfigurationManagement.Messaging.dll" | Out-Null [void] [Reflection.Assembly]::LoadFile("x:\Microsoft.ConfigurationManagement.Messaging.dll") | out-null } [Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
Setting up Objects
Now that we have the necessary references and using statements we’ll need to create the objects used through the request:
C#:
HttpSender hSender = new HttpSender(); ConfigMgrContentLocationRequest clr = new ConfigMgrContentLocationRequest(); ConfigMgrContentLocationReply cmReply = new ConfigMgrContentLocationReply();
Powershell:
$httpSender = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Sender.Http.HttpSender $clr = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ConfigMgrContentLocationRequest $cmReply = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationReply
Setting Up the Request
Now with our objects setup, we can begin formulating the request. The first thing we’ll need to do is run the “Discover” method of the ConfigMgrContentLocationRequest object. Essentially this grabs the systems IP Addresses, Current AD Site, AD Domain and AD Forest.
Nothing too difficult right? Well, not exactly! The AD Domain and AD Forest discovery uses the ADSI COM object. Still no problem though, right? Well, not if you intend to use this within WinPE. We’ve got 2 options then: (1) Add the ADSI Plugin to our WinPE image (Thanks to Johan Arwidmark), or (2) build a try catch.
Since the ADSI plugin is not included with WinPE and not supported by Microsoft, we’ll just use a simple try/catch. However, there’s another catch, we’ll need to specify the domain and forest strings.
C#:
private string myDomain = "mydomain.com"; private string myForest = "myForest.com"; try { clr.Discover(); } catch { clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses(); clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite(); clr.LocationRequest.Domain = new ContentLocationDomain() { Name = myDomain }; clr.LocationRequest.Forest = new ContentLocationForest() { Name = myForest }; }
Powershell:
[string]$myDomain = "mydomain.com" [string]$myForest = "myforest.com" try { $clr.Discover() } catch { $clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses() $clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite() $clr.LocationRequest.Domain = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationDomain $clr.LocationRequest.Domain.Name = $myDomain $clr.LocationRequest.Forest = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationForest $clr.LocationRequest.Forest.Name = $myForest }
Next we’ll need to setup the parameters for the request. For this we’ll need a Management Point fqdn, Management Point Site Code, Package ID, and Package Source Version.
C#:
clr.SiteCode = mySiteCode; clr.Settings.HostName = myManagementPoint; clr.LocationRequest.Package.PackageId = packageID; clr.LocationRequest.Package.Version = packageVersion;
Powershell:
$clr.SiteCode = $ManagementPointSiteCode $clr.Settings.HostName = $ManagementPoint $clr.LocationRequest.Package.PackageId = $PackageID $clr.LocationRequest.Package.Version = $PackageVersion
Now, perhaps one of my favorite things here, is that we can manually set the AD Site code and get a location request as if we were at a different location. This is especially nice if we don’t have access to systems in those specific AD Sites and want to test!
C#:
if (myCustomAdSite != null) { clr.LocationRequest.ContentLocationInfo.ADSite = myCustomAdSite; }
Powershell:
if ($myCustomAdSite -ne $null) { $clr.LocationRequest.ContentLocationInfo.ADSite = $myCustomAdSite }
Next we’ll perform a validate on the message (ConfigMgrContentLocationRequest). This call validates that the message we have setup is good. If Validate fails, when the message is sent, it will undoubtedly fail as well. After validate we’ll send the request.
C#:
clr.Validate((IMessageSender)hSender); cmReply = clr.SendMessage(hSender);
Powershell:
$clr.Validate([Microsoft.ConfigurationManagement.Messaging.Framework.IMessageSender]$httpSender) $cmReply = $clr.SendMessage($httpSender)
Now we have our returned result! It will be returned as a string but in XML format. We can easily cast this into an XML object and do whatever we wish! Before returning the value, I’ve had some issues casting this due to a trailing ” ” (Space) at the end of the string. Below is a quick loop that’ll take care of that, should it exist.
C#:
string response = cmReply.Body.Payload.ToString(); while (response[response.Length - 1] != '>') { response = response.TrimEnd(response[response.Length - 1]); } Console.WriteLine(response);
Powershell:
$response = $cmReply.Body.Payload.ToString() while($response[$response.Length -1] -ne '>') { $response = $response.TrimEnd($response[$response.Length -1]) } Write-Output $response
Now we’ve got a nice neat Content Location Request! As you see below there’s quite a bit of information here. My primary accomplishment was checking that “SiteLocality” was “LOCAL” and there were LocationRecords.
Config Mgr 2012 R2 SP1:
<ContentLocationReply SchemaVersion="1.00"> <ContentInfo PackageFlags="1073741825"> <ContentHashValues/> </ContentInfo> <Sites> <Site> <MPSite IISSSLPreferedPort="443" IISPreferedPort="80" SiteLocality="LOCAL" MasterSiteCode="P01" SiteCode="P01"/> <LocationRecords> <LocationRecord> <ADSite Name="MyADSite"/> <IPSubnets> <IPSubnet Address="xxx.xxx.xxx.xxx"/> <IPSubnet Address=""/> </IPSubnets> <Metric Value=""/> <Version>8239</Version> <Capabilities SchemaVersion="1.0"> <Property Name="SSLState" Value="0"/> </Capabilities> <ServerRemoteName>myServer.Domain.com</ServerRemoteName> <DPType>SERVER</DPType> <Windows Trust="0"/> <Locality>LOCAL</Locality> </LocationRecord> </LocationRecords> </Site> </Sites> <RelatedContentIDs/> </ContentLocationReply>
Config Mgr Technical Preview 4 (v1511):
<ContentLocationReply SchemaVersion="1.00"> <BoundaryGroups BoundaryGroupListRetrieveTime="2015-12-06T22:19:36.400"> <BoundaryGroup GroupID="16777217"/> <BoundaryGroups/> <ContentInfo PackageFlags="16777216"> <ContentHashValues/> </ContentInfo> <Sites> <Site> <MPSite IISSSLPreferedPort="443" IISPreferedPort="80" SiteLocality="LOCAL" MasterSiteCode="P01" SiteCode="P01"/> <LocationRecords> <LocationRecord> <URL Signature="http://myServer.Domain.com/SMS_DP_SMSSIG$/XXX#####" Name="http://myServer.Domain.com/SMS_DP_SMSPKG$/XXX#####"/> <ADSite Name="MyADSite"/> <IPSubnets> <IPSubnet Address="xxx.xxx.xxx.xxx"/> <IPSubnet Address=""/> </IPSubnets> <Metric Value=""/> <Version>8325</Version> <Capabilities SchemaVersion="1.0"> <Property Name="SSLState" Value="0"/> </Capabilities> <ServerRemoteName>myServer.Domain.com</ServerRemoteName> <DPType>SERVER</DPType> <Windows Trust="0"/> <Locality>LOCAL</Locality> </LocationRecord> </LocationRecords> </Site> </Sites> <RelatedContentIDs/> </ContentLocationReply>
These seem to differ ever so slightly from 2012 R2 (SP1) to v1511. There’s an added “BoundaryGroups” element and “URL Signature” Element as well. However, nothing that would (at least right now) prevent this code from running and working!
Full Code
C#:
// Set up the objects HttpSender hSender = new HttpSender(); ConfigMgrContentLocationRequest clr = new ConfigMgrContentLocationRequest(); ConfigMgrContentLocationReply cmReply = new ConfigMgrContentLocationReply(); // Discover (the try/catch is in case the ADSI COM // object cannot be loaded (WinPE). try { clr.Discover(); } catch { clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses(); clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite(); clr.LocationRequest.Domain = new ContentLocationDomain() { Name = myDomain }; clr.LocationRequest.Forest = new ContentLocationForest() { Name = myForest }; } // Define our SCCM Settings for the message clr.SiteCode = mySiteCode; clr.Settings.HostName = myManagementPoint; clr.LocationRequest.Package.PackageId = packageID; clr.LocationRequest.Package.Version = packageVersion; // If we want to "spoof" a request from a different // AD Site, we can just pass that AD Site in here. if (myCustomAdSite != null) { clr.LocationRequest.ContentLocationInfo.ADSite = myCustomAdSite; } // Validate clr.Validate((IMessageSender)hSender); // Send the message cmReply = clr.SendMessage(hSender); // Get response string response = cmReply.Body.Payload.ToString(); while (response[response.Length - 1] != '>') { response = response.TrimEnd(response[response.Length - 1]); } Console.WriteLine(response);
Powershell:
# Set up the objects $httpSender = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Sender.Http.HttpSender $clr = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ConfigMgrContentLocationRequest $cmReply = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationReply # Discover (the try/catch is in case the ADSI COM # object cannot be loaded (WinPE). try { $clr.Discover() } catch { $clr.LocationRequest.ContentLocationInfo.IPAddresses.DiscoverIPAddresses() $clr.LocationRequest.ContentLocationInfo.ADSite.DiscoverADSite() $clr.LocationRequest.Domain = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationDomain $clr.LocationRequest.Domain.Name = $myDomain $clr.LocationRequest.Forest = New-Object -TypeName Microsoft.ConfigurationManagement.Messaging.Messages.ContentLocationForest $clr.LocationRequest.Forest.Name = $myForest } # Define our SCCM Settings for the message $clr.SiteCode = $ManagementPointSiteCode $clr.Settings.HostName = $ManagementPoint $clr.LocationRequest.Package.PackageId = $PackageID $clr.LocationRequest.Package.Version = $PackageVersion # If we want to "spoof" a request from a different # AD Site, we can just pass that AD Site in here. if ($myCustomAdSite -ne $null) { $clr.LocationRequest.ContentLocationInfo.ADSite = $myCustomAdSite } # Validate $clr.Validate([Microsoft.ConfigurationManagement.Messaging.Framework.IMessageSender]$httpSender) # Send the message $cmReply = $clr.SendMessage($httpSender) # Get response $response = $cmReply.Body.Payload.ToString() while($response[$response.Length -1] -ne '>') { $response = $response.TrimEnd($response[$response.Length -1]) } Write-Output $response
Enjoi!