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:
- 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
- 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.