Finding Empty Device Collections in SCCM

, , , , , , , ,

Today’s script is going to be almost a one-liner, but a very useful one.  I had found SQL queries that would do this same thing, but I frequently find myself working on a computer that does not have the SQL management software installed on it, and this query doesn’t run cleanly through the SCCM Management software.  However, I’m always on a computer with PowerShell and since this runs through WMI, you don’t even need to connect to the SCCM site code drive.
Having the right amount of device collections in SCCM is a bit of a balancing act. You don’t want so few collections that you don’t have a good way to sort out your clients logically, but you don’t want so many that you’ve gunked up your database with entries you’re never actually going to use. On top of that, some of the companies I’ve contracted with have had many collections that never held a single client. This script will go through and find those empty collections for you.   If you just want the PS1 file, click HERE.

Step 1: Initializing a few variables
Typically, if I’m working with SCCM through WMI, I like to setup a few variables in my shell right away to make my life easier and save some keystrokes throughout the day.
$siteServer = “PriServer1″ # whatever the name of your primary server is
$namespace = “root/SMS/site_TST” # root/SMS/site_  whatever your site code is

If you’re constantly making WMI calls to SCCM, being able to just enter those variables instead of typing it all out can make things easier. At least, that’s what I’ve found.

Step 2: Get your SCCM Device Collections
This script is going to be done on a single line, but I’ll break it out into chunks first. The first thing we need to do is pull all of the device collections from SCCM.
Get-WmiObject -Namespace $namespace -ComputerName $siteServer -Class SMS_Collection -filter ‘CollectionType =2’
If you wanted to pull user collections, you would change ‘CollectionType = 2’ to ‘CollectionType = 1’
If you want to get both sets of collections, just leave out the -filter portion entirely.

Step 3: Check Those Collections for Members
There are a couple ways you could do this. You could pipe your collections to a ForEach loop with an if statement in it. Alternatively, I choose to just pipe the first cmdlet into a where statement that does it for me.
| where {$colID = $_.CollectionID;(Get-WmiObject -Namespace $namespace -ComputerName $siteServer -class SMS_FullCollectionMembership -filter “CollectionID=’$colID'”).count -eq 0}

What this is doing is setting a variable called “$colID” to be the collection ID number from the collection passed through the pipe.  Then, we’re going to do a WMI query to the SMS_FullCollectionMembership class, which is a class that maintains a list of every device/user to collection association. We’re adding a filter to only show collection IDs that match the one we want, counting up all the members, and if that number equals 0, we know the collection has nothing in it.

Step 4: Profit
If you just stop there, you have a set of list-formatted data that, while not pretty, does contain all the information and objects you need to do work. It’s not the easiest thing to read, but it does give you a very solid information dump about the empty collection that looks something like this:

device collections


Chances are, if you’re generating this report for a manager, customer, etc, they’re going to want it formatted a bit nicer, and they’ll probably want it in an Excel spreadsheet.  The easiest way to do this is:
| select Name,CollectionID | export-csv C:usersMyUserDocumentsEmptyCollections.csv -noTypeInformation

 Alternatively, if you get the go-ahead to remove these device collections, instead of piping to a select/export-csv statement, you can pipe to remove-wmiobject instead.
For the love of god, run it with -whatif first.  

Thanks for reading, and if you have any questions, feel free to post them in the comments. 

Using Custom Objects for Fun and Profit

, , , , ,

Custom objects were one of those things I never really saw the point of when I read about them. It wasn’t until I actually messed around with them that I really understood their uses. Custom objects are especially good for generating reports that pull data from multiple sources.  For example, let’s say your manager has requested a report of all the computers in your Accounting department. He wants to know their hostname, their IP Address, their Make/Model, and their Serial Number.  Now, there’s no built in function or class (that I know of) that will return all of those pieces of information, so we’ll have to pull from multiple data sets.  For this example, we’re going to need Active Directory, the Win32_BIOS class, and the Win32_ComputerSystem class. For the sake of argument, let’s say we’ll also need the Get-CompOU script from the previous post and the hostname of a computer in the Accounting department’s OU to get started.
An example asset report script can be downloaded here:

Step 1: Generate a list of all the computers needed for this report
$allComps = Get-ADComputer -SearchBase (Get-CompOU AccountingPC1) -filter * -properties * 

This gives us an array of all the computer objects hosted in the same OU as the Accounting PC we started with. We can get the hostname and IP information just from these objects, but we need more.

Step 2: Enumerate all the computer objects and start data mining
ForEach ($comp in $allComps)
        $SWMI = Get-WmiObject -ComputerName $comp.Name -class Win32_ComputerSystem
$BWMI = Get-WmiObject -ComputerName $ -class Win32_BIOS

We’re not quite done with the loop yet, but this shows how we’re invoking the WMI classes needed for each computer.  Step 3 takes place within the same loop.

Step 3: Create the custom object to hold our required data
$object = New-Object -TypeName PSObject
$object | Add-Member -MemberType NoteProperty -Name “Hostname” -Value $comp.Name
$object | Add-Member -MemberType NoteProperty -Name “DNSName” -Value $comp.DNSHostName
$object | Add-Member -MemberType NoteProperty -Name “Serial” -Value $BWMI.SerialNumber
$object | Add-Member -MemberType NoteProperty -Name “IPAddress” -Value $comp.IPV4Address 
$object | Add-Member -MemberType NoteProperty -Name “Make” -Value $SWMI.Manufacturer
$object | Add-Member -MemberType NoteProperty -Name “Model” -Value $SWMI.Model 

$allObj += $object
 This one is pretty straight forward. It’s a lot of text just to say that you’re adding properties to an item you created, assigning those properties values based on multiple classes created in Step 2, and then adding that object to an array of objects.  You can type $allObj = @() at the beginning of your script, but it isn’t required.

Step 4: Profit
At this point, we can dump our report out to a CSV, or we can just output to the screen.  Typing $allObj will just output to the screen, but if we want to make a report our management will be proud of:
$allObj | export-csv -path C:usersAdminDocumentsAssetReport.csv -NoTypeInformation

And we’re done!  Now, if you’re working in an environment with SCCM or other management software, these reports might be more easily generated by querying that database. However, this will give you up to the minute accuracy as the WMI queries are done live.  If there’s anything you want to see, leave me a comment, and I’ll add that to my next post.

ALSO CHECK : A quick (and useful) PowerShell script

A quick (and useful) PowerShell script

, ,

PowerShell script

Whether you’ve been in the IT game for years or are just starting out, there are a few simple tools you’ll find yourself relying on almost every day to do tasks so basic, you’ll wonder why Microsoft doesn’t make them just a little easier to do.   The object of this post is to give you a couple small tools that will make your life easier.

Cmdlet 1: Get-CompOU – return the Organizational Unit of any hostname
If you manage an Active Directory network of any size, you’ll probably wind up troubleshooting Group Policy, and one of the major things that decides what policies your computer receives is the Organizational Unit (OU) where your computer resides.  Personally, I’m a lazy IT guy. I don’t like opening ADUCs, right-clicking the domain, clicking search, typing my computer name (only to realize I forgot to select “Computer” from the drop down), clicking Search, right clicking my computer name, selecting properties, and then finding out the Object tab isn’t there because I forgot to turn on Advanced Features, which means I get to close out of my search and start the whole thing over again.  Even once you’ve found it, now you have to memorize or copy it so you can find your problem child computer and move it to the right OU.
Alternatively, you can have some simple PowerShell script attached to your profile that will do it all in one line.  You can download the PowerShell script here:, but I’ll also go through what makes it tick.


Function Get-CompOU ($computerName){
$comp = get-adcomputer $computerName
if ($comp -eq $null) {write-host -ForegroundColor Red “Computer object $computerName was not found”; return}
else {
$compOU = get-adorganizationalunit  ($comp.distinguishedname.substring($comp.distinguishedname.indexof(“,”)+1))
return $compOU

First off, I make all of my PowerShell script that aren’t quick and dirty one-liners as Functions. This means that once I’ve imported my profile, I can just type “get-compou [somehostname],” and my computer will know what to do just the same as if I had typed “get-childitem” or “get-wmiobject.”  The “$computerName” variable is what’s passed as the first argument.   The second line calls get-adcomputer to return the AD computer object of the host name. Then, we check to make sure the computer name was actually found. If it wasn’t, it’ll tell you and return nothing.  If it was found, then we find the OU that matches the one given in the Distinguished Name of the computer and return that object.
In case you’re wondering why we don’t just return the OU name that we extracted from the Distinguished Name, it’s because I want to return an actual AD OrganizationalUnit object instead of just a string. That gives us a lot more power and freedom with what we can do. It’s also why we use return at the end instead of a write-host.
For example:  TechPC1 is able to run regedit no problem.  TechPC2 gets an error stating that regedit is disabled by his system administrator, even though 1 and 2 should be getting the same policies.  Our Sys Admin runs Get-CompOU TechPC1 and finds that PC1 is in the correct OU.  Running Get-CompOU TechPC2 reveals that PC2 was accidentally placed in the same OU as the regular production user machines.  From here, our Sys Admin types  get-adcomputer TechPC2 | move-adobject -TargetPath (Get-CompOU TechPC1). 
Viola! That computer has been moved.  Personally, I find this easier than all the right clicking, searching, copying, and manually moving. The nice thing with PowerShell being so extensible is that the foreachobject cmdlet gives you the ability to run this PowerShell script against an entire text file of names, or generate a report of every computer in a given OU.
    I hope you’ve found this useful. Stay tuned for the next post.


ALSO CHECK : Using Custom Objects for Fun and Profit