Working with SCCM 2012 R2 and SCCM 2016, there are PowerShell cmdlets to export several types of objects from System Center Configuration Manager (SCCM). Alas, the boundary group Cmdlets just aren’t there yet.

Here are a few examples of SCCM objects that support exporting.

CommandType Name Version Source
Cmdlet Export-CMAntimalwarePolicy 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMApplication 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMBaseline 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMCollection 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMConfigurationItem 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMDriverPackage 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMPackage 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMQuery 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMSecurityRole 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMTaskSequence 5.0.8373.1189 ConfigurationManager
Cmdlet Export-CMWindowsEnrollmentProfile 5.0.8373.1189 ConfigurationManager

Here are the commands that work with boundaries and boundary groups.

CommandType Name Version Source
Cmdlet Add-CMBoundaryToGroup 5.0.8373.1189 ConfigurationManager
Cmdlet Get-CMBoundary 5.0.8373.1189 ConfigurationManager
Cmdlet Get-CMBoundaryGroup 5.0.8373.1189 ConfigurationManager
Cmdlet New-CMBoundary 5.0.8373.1189 ConfigurationManager
Cmdlet New-CMBoundaryGroup 5.0.8373.1189 ConfigurationManager
Cmdlet Remove-CMBoundary 5.0.8373.1189 ConfigurationManager
Cmdlet Remove-CMBoundaryFromGroup 5.0.8373.1189 ConfigurationManager
Cmdlet Remove-CMBoundaryGroup 5.0.8373.1189 ConfigurationManager
Cmdlet Set-CMBoundary 5.0.8373.1189 ConfigurationManager
Cmdlet Set-CMBoundaryGroup 5.0.8373.1189 ConfigurationManager

It looks promising, but when I was trying to work with boundaries and their groups, I found that there was a disconnect in that when using the native SCCM PowerShell cmdlets, that I could not determine which boundaries were apart of which boundary group. PowerShell is great in many ways, so instead of having to wait for someone to write these commands, lets tell PowerShell to get the information we require from deeper in the system. To do this, we’ll leverage WMI and target the SMS classes.

Searching through MSDN, I found the classes I needed, 3 of them to be exact.

WMI Class MSDN URL PowerShell Command
SMS_Boundary https://msdn.microsoft.com/en-us/library/hh442841.aspx Get-CMBoundary
SMS_BoundaryGroupSiteSystems https://msdn.microsoft.com/en-us/library/hh442774.aspx Get-CMBoundaryGroup
SMS_BoundaryGroupMembers https://msdn.microsoft.com/en-us/library/hh458110.aspx?f=255&MSPPError=-2147217396 Does not exist

The magic to getting this working is what I call a link table, in this case SMS_BoundaryGroupMembers. This linking table has the 2 magic columns we are looking for GroupID and BoundaryID. Each Boundary Group that you have defined will have multiple GroupID entries in the SMS_BoundaryGroupmembers sms table with an associated BoundaryID for the Boundary member/object that is a member of the boundary group.

PS XYZ:\DeviceCollection> Get-WmiObject -ComputerName $server -Namespace root\sms\site_$siteCode -Query "SELECT * FROM SMS_BoundaryGroupMembers" | Sort-Object -Property GroupId | Select-Object -First 3
__GENUS : 2
__CLASS : SMS_BoundaryGroupMembers
__SUPERCLASS : SMS_BaseClass
__DYNASTY : SMS_BaseClass
__RELPATH : SMS_BoundaryGroupMembers.BoundaryID=16248401,GroupID=16248217
__PROPERTY_COUNT : 2
__DERIVATION : {SMS_BaseClass}
__SERVER : SCCM01
__NAMESPACE : root\sms\site_XYZ
__PATH : \\SCCM01\root\sms\site_XYZ:SMS_BoundaryGroupMembers.BoundaryID=16248401,GroupID=16248217
BoundaryID : 16248401
GroupID : 16248217
PSComputerName : SCCM01

__GENUS : 2
__CLASS : SMS_BoundaryGroupMembers
__SUPERCLASS : SMS_BaseClass
__DYNASTY : SMS_BaseClass
__RELPATH : SMS_BoundaryGroupMembers.BoundaryID=16248400,GroupID=16248217
__PROPERTY_COUNT : 2
__DERIVATION : {SMS_BaseClass}
__SERVER : SCCM01
__NAMESPACE : root\sms\site_XYZ
__PATH : \\SCCM01\root\sms\site_XYZ:SMS_BoundaryGroupMembers.BoundaryID=16248400,GroupID=16248217
BoundaryID : 16248400
GroupID : 16248217
PSComputerName : SCCM01

__GENUS : 2
__CLASS : SMS_BoundaryGroupMembers
__SUPERCLASS : SMS_BaseClass
__DYNASTY : SMS_BaseClass
__RELPATH : SMS_BoundaryGroupMembers.BoundaryID=16248402,GroupID=16248217
__PROPERTY_COUNT : 2
__DERIVATION : {SMS_BaseClass}
__SERVER : SCCM01
__NAMESPACE : root\sms\site_XYZ
__PATH : \\SCCM01\root\sms\site_XYZ:SMS_BoundaryGroupMembers.BoundaryID=16248402,GroupID=16248217
BoundaryID : 16248402
GroupID : 16248217
PSComputerName : SCCM01

Let’s take a look at some code to get all this information we need from WMI and assemble it into a useful script. First off the WMI classes reside on the SCCM Server, so we’ll need to tell our Get-WmiObject PowerShell call to run against that server and to connect into the correct SCCM Site. Luckily WMI supports simple SQL style syntaxes and this allowed me to JOIN all the 3 WMI classes with only 1 call. Basically I’m asking for all the defined boundaries, then I’ll join that result to the linking table. Now to wrap our heads around this, we have all the boundaries with any associated boundary group ids. Well we need to take this one step further in order to have useful information about the boundary group, like its name. So the next FULL JOIN will add all the boundary group information where we can find an associated match in the linking table.

$server = "sccm01"
$siteCode = "xyz"
$boundarytype = @("IPSubnet", "ADSite", "IPV6Prefix", "IPRange")

$members = Get-WmiObject -ComputerName $server -Namespace root\sms\site_$siteCode -Query "SELECT Boundary.*, BoundaryGroup.* FROM SMS_Boundary boundary LEFT JOIN SMS_BoundaryGroupMembers BGroupMembers ON boundary.boundaryId = BGroupMembers.boundaryId FULL JOIN SMS_BoundaryGroup BoundaryGroup ON BGroupMembers.GroupId = BoundaryGroup.GroupId"

Now that we have all this information returned from WMI, the problem is that each boundary has multiple entries for each boundary group. I wanted to clean this up so that when I look at a specific boundary in the WMI output, I can see all the boundary groups it’s associated with. To do this I do a few things in the next block of code:

  1. I Create a hash table using the boundary entry as the main key. This information is duplicated in each WMI table, so it makes sense to use that as the key
  2. In order to add the boundary information to the key as a value, in addition to an array of boundary group information stored in a property called GroupMembershipInfo, I have to create a custom PowerShell Object
$BoundaryMembershipInfo = @{}
foreach ($member in $members) {
    if ($BoundaryMembershipInfo[$member.Boundary.BoundaryId.ToString()]) {
        $BoundaryMembershipInfo[$member.Boundary.BoundaryId.ToString()].GroupMembershipInfo += $member.BoundaryGroup
    } else {
        $BoundaryInfo = New-Object PSObject
 
        # Save all the Boundary Information
        foreach ($Property in $member.boundary.PSObject.Properties) {
            $BoundaryInfo | Add-Member -MemberType NoteProperty -Name $Property.Name -Value $Property.Value
        }

        # Create an array to save boundary group membership information
        $BoundaryInfo | Add-Member -MemberType NoteProperty -Name "GroupMembershipInfo" -Value @($member.BoundaryGroup)
        
        $BoundaryMembershipInfo[$member.Boundary.BoundaryId.ToString()] = $BoundaryInfo
    }
}

Almost there! We’ve just queried WMI, and essentially reformatted the query result into a hash table. Hash tables are great and I use them for quite a few things. In this case I used the hash table to create a unique “array” of boundary information. Now to really use the information and be able to pass it through the pipeline, we’re going to convert it into an array.

#Convert Hash Table into a simple array of values so that we can easily use it within the pipeline
$BoundaryMembershipInfo = [array]$BoundaryMembershipInfo.Values

Now let’s see what information we have in our array, oops I mean what defined boundaries are apart of which boundary group.

#Output the Information
$BoundaryMembershipInfo | select-object value, Displayname, @{N="BoundaryType"; e={$boundaryType[$_.BoundaryType]}}, @{N="BoundaryGroups"; e={($_.GroupMembershipInfo).Name -join "; "}} | Out-GridView -Title "Boundary Output"

And there we go. We are now able to use PowerShell to query the SCCM boundaries and also determine which SCCM Boundary Group they are a member of.