Remove Unreferenced packages from Distribution Points

DownArrowThe story

Our distribution points were getting pretty full, and I wanted to see if I could clean them up before requesting more disk space. I wanted to go out and make sure we remove any packages that were on our DP’s and DP Groups that were not referenced by a task sequence or had an active deployment. I mean, why keep them there?

The Solution

So, to start, I had to figure out a SQL query that would get me the PackageID’s for packages that were not referenced by a task sequence and not in an active deployment. I try to avoid not in queries when I can, so I first wanted to create a query for PackageID’s that were referenced by a task sequence. Now, since I’m just working on packages, I had to join two tables and specify a PackageType of 0.

That got me the packages referenced by a task sequence, now for the packages that have active deployments.

By combining the two queries above, I get a list of packages that should be on our DP’s. I find out what packages are on the DP’s that should not be there (and yes, I have to use not in):

That get’s me all the packages that are not supposed to be on the DP’s! We take all this and select the PackageID’s from V_Package where the PackageID is in the result of the query above and the PackageType = 0. We end up with:

And there it is! All the PackageID’s that are out there on our DP’s that have no deployments and are not referenced by a task sequence! This is the list we will use to remove them!

The Script

OK, I have put the entire script below and will be referencing the line #’s in there, if you want a copy, you can either cut and paste from below or go to my Bitbucket link above and grab it from there.

This script has 1 required and three optional parameters:

  • The first (and only required) is the SiteServer: This is the site server where we are going to be cleaning up.
  • Next is the LogLevel, this is the level of information to be logged, 1 = Informational, 2 = Warning (default), and 3 = Error.
  • LogFileDir is where you want the log written to, this defaults to C:\Temp
  • Finally, we have ClearLog, when you specify this to delete the current logfile, if it exists. Remember, the logfile is going to be <scriptname>.log

OK, let’s look at what this script does.

Lines 1 to 53 are the initial lines to describe the function and define the parameters.

I’d like to jump down to line 301 and come back to the functions as we encounter them. Lines 301 to 305 are setting the logfile variable to “LogFileDir\ScriptName.log.” It does checks to make sure there is a \ at the end of LogFileDir. We also remove the existing log if the flag is set and it exists.

Line 308, we get our first use of the New-LogEntry function. This function is described in an earlier post found here.

Next (line 309), we determine the site code by grabbing it from the SiteServer and then we import the SCCM PowerShell Module. Line 317, we save our current location and then change to the Site: drive.

Since we will be querying WMI several times, and the same ComputerName and Namespace parameters will be used frequently, we save them in a hash table called WMIQueryParameters on lines 320-323.

Lines 326 and 327 get the DataBaseServer and Database names from the site server.

We’re ready to get that list of packages! Lines 330 to 345 set the SQLCommand variable to our query above.

Line 347 calls the Get-SQLQuery function  (lines 55 to 100). This function creates a connection, then a command object. This is passed the SQLCommand and then opened. We pull in the data and return the table.

Now, $PackageIDs has a list of Packages that need to be removed. Starting at line 348, we cycle through each ID, logging the package we are processing (line 350) and then we call IsPKGReferenced to make sure nothing has changed. This function (lines 182 to 241) checks for a task sequence reference or an active distribution. If either of those exist, it returns $true, otherwise it returns $false.

Finally, line 355 calls Remove-CMNDPPackage (lines 112 to 179) and removes the package. This function first gets the current list of DP’s/DP Group’s the package is on (line 144-145). Line 148, we verify that we got a return value, if not, then this package isn’t on any DP’s. Now, since the value for the name is in different formats for DP’s and DP Groups, we have to clean it up. First, if we see a ‘\’ in the name, we know it’s a DP and need to extract just the name from there. That is what lines 152 to 154 is doing. If, on the other hand, it doesn’t have a ‘\’ in the name, it’s a DP group and we can just use the name (line 158). Now (lines 160 to 164), if the ContentServerType is 1, it’s on a distribution Point, we use the DistributionPointName parameter, otherwise (Lines 165 to 169), we use the Distribution PointGroupName parameter of Remove-CMContentDistribution cmdlet. You will see that it takes a couple of seconds for this command to run.


I ran this against our environment and it removed 286 packages from our DP’s. Ended up running for over an hour and giving me around 20GB of space.

%d bloggers like this: