The official blog of the Microsoft System Center Configuration Manager Product Group
In System Center 2012 Configuration Manager, we’ve added the capability to automatically remove software update content from distribution points when that content is related to expired updates. This process helps manage drive space on your distribution points by removing any content you no longer need. It’s particularly helpful for Endpoint Protection definition updates, given their frequency of release, deployment, and expiration. In this blog, I’m going to walk you through exactly how the process works, and I’m also going to provide a script you can use to clean up source locations related to obsolete updates, as our out-of-box process does not manage source content.
The first part of the process for managing content related to expired updates is getting expired updates out of any deployed update groups. First, we never delete any expired update associated with an active deployment, as we don’t want to remove anything associated with your deployments. It could be disconcerting if updates simply disappeared from your deployments and you had no idea why, so we just don’t do it. The step to make expired updates removable, however, is straightforward, and it should be part of a monthly process. To remove expired updates from all deployments in only a couple of clicks, do the following. Note: When using Auto Deployment Rules to deliver definition updates, where you should be re-using the same update group each time the rule runs, expired updates are automatically removed from the update group each time the rule runs.
Next, there are four phases to removing expired updates and its related content, and those are: the expiration action, tomb-stoning, deletion, and then source cleanup, which is a scripted action. At a high level, updates that have been expired and that aren’t part of an active deployment will be deleted 7 days after they are expired. There is no specific or configurable maintenance task to do this—it’s an automated process that crosses multiple ConfigMgr components. Let’s walk through the process at a detailed level so you understand how it works.
Removed 106 unreferenced updates SMS_WSUS_SYNC_MANAGER 3/30/2012 12:06:15 AM
Deleting old expired updates... SMS_WSUS_SYNC_MANAGER 3/30/2012 12:06:16 AM Deleted 80 expired updates SMS_WSUS_SYNC_MANAGER 3/30/2012 12:06:20 AM Deleted 80 expired updates total SMS_WSUS_SYNC_MANAGER 3/30/2012 12:06:20 AM
Found notification file C:\Program Files\Microsoft Configuration Manager\inboxes\objmgr.box\16824032.CIN SMS_OBJECT_REPLICATION_MANAGER 3/30/2012 12:06:11 AM Completed processing CIN notification files SMS_OBJECT_REPLICATION_MANAGER 3/30/2012 12:07:31 AM Successfully deleted SoftwareUpdate 3da7abb4-d643-422c-9e6d-a5f838fb71a1 SMS_OBJECT_REPLICATION_MANAGER 3/30/2012 12:07:32 AM
Expired update and associated content cleanup in Configuration Manager 2012 is a built-in mechanism to help keep your console, database, distribution points and (with the script) source directories as clean as possible. Hopefully, you better understand the manual steps required to do this, as well as how all of the automated pieces work together to manage expired updates and content.
option explicit
dim CRLF: CRLF=chr(13) & chr(10)
dim WSH: set WSH = CreateObject("WScript.Shell")
dim FSO: set FSO = CreateObject("Scripting.FileSystemObject")
dim WMI: set WMI = GetObject("winmgmts:/root/SMS")
dim Prov: set Prov = nothing
dim I, f
for each I in WMI.ExecQuery("select * from SMS_ProviderLocation where ProviderForLocalSite=TRUE")
set Prov = I
next
if Prov is nothing then err.Raise 438,"No provider found for the local site"
dim SiteCode: SiteCode = Prov.SiteCode
dim SiteServer: SiteServer = Prov.Machine
set WMI = GetObject("winmgmts:" & Prov.NamespacePath)
dim Pkg
for each Pkg in WMI.ExecQuery("select * from SMS_SoftwareUpdatesPackage")
Log "Processing package " & Pkg.PackageID & " - " & Pkg.Name
' remove orphaned package-to-content relations (not associated with CIs)
for each i in WMI.ExecQuery("select pc.* from SMS_PackageToContent pc left join [CR]SMS_CIToContent cc on cc.ContentID=pc.ContentID where pc.PackageID=""" & Pkg.PackageID & """ and [CR]cc.ContentID is null")
i.Delete_
Log "Package source path: " & Pkg.PkgSourcePath
dim srcFolder: set srcFolder = FSO.GetFolder(Pkg.PkgSourcePath)
dim foldersToDelete: set foldersToDelete = CreateObject("Scripting.Dictionary")
foldersToDelete.CompareMode = vbTextCompare
' collect subfolders currently in pkg source
for each i in srcFolder.SubFolders
if i.Attributes=16 and i.Name<>"." and i.Name<>".." then
'Log "Existing folder " & i.Name
foldersToDelete.Add i.Name, nothing
end if
' exclude subfolders associated with active content
for each i in WMI.ExecQuery("select pc.* from SMS_PackageToContent pc where [CR]pc.PackageID=""" & Pkg.PackageID & """")
'Log "Excluding active folder " & i.ContentSubFolder
if foldersToDelete.Exists(i.ContentSubFolder) then [CR]foldersToDelete.Remove(i.ContentSubFolder)
f = vbFalse
' delete remaining folders
if foldersToDelete.EXists(i.NAme) then
Log "Deleting orphaned subfolder " & i.name
i.Delete
f = vbTrue
if f = vbTrue then
Log "Refreshing package " & pkg.PackageID
pkg.RefreshPKgSource
Log "cleanup completed"
'================== logging support ==============
dim logMode
dim logWindow
sub Log(msg)
if IsEmpty(logWindow) then call LogInit
select case logMode
case "console": WScript.StdOut.WriteLine msg
case "window": if IsObject(logWindow) then [CR]logWindow.document.all.logLines.innerHtml = logWindow.document.all.logLines.innerHTML & msg & [CR]"<br/>"
end select
end sub
sub LogInit
if not WScript.Interactive then
logMode = "none"
elseif lcase(right(WScript.FullName, 11))="cscript.exe" then
logMode = "console"
else
set logWindow = WScript.CreateObject("InternetExplorer.Application", [CR]"Log_")
with logWindow
.Navigate("about:blank")
.Document.Title = WScript.ScriptName
.AddressBar = false
.ToolBar = false
.StatusBar = false
.Resizable = true
.Visible = 1
do while .Busy: WScript.Sleep 100: loop
.document.body.innerHTML = "<div id=""logLines"" [CR]style=""font:10pt sans-serif;text-align:left;"" />"
end with
logMode = "window"
sub LogTerm
if IsObject(logWindow) then logWindow.Quit: set logWindow = nothing
' provide a way of terminating the script in windowed mode - closing the log window will [CR]terminate the script
sub Log_onQuit
WScript.Quit
--Jason Githens
This posting is provided "AS IS" with no warranties, and confers no rights.
Finally Someone has answered my calls.....i have been waiting for this for sooooooo long. it is going to make life easier in ConfigMgr. thanks again.
Thanks for that script. But unfortunately, when I start it I have the following failure:
Error: Unexpected ‘Next’
Code: 800A041F
It the next after:
if foldersToDelete.Exists(i.ContentSubFolder) then
foldersToDelete.Remove(i.ContentSubFolder)
JBAB - it looks like the formating of that script is all messed up. It added Line Returns where they should not be.
The original line was:
if foldersToDelete.Exists(i.ContentSubFolder) then foldersToDelete.Remove(i.ContentSubFolder)
With a Line Return it must have an End If like this:
Scanning through the code I don't see any other problems like this. But to fix yours either put that all on one line or add the End If.
Scott
I take my No More Line issues comment back (for IF statements it is true but not other statements).
Here are all the lines that should be 1 line to make the script work properly
for each i in WMI.ExecQuery("select pc.* from SMS_PackageToContent pc left join
SMS_CIToContent cc on cc.ContentID=pc.ContentID where pc.PackageID=""" & Pkg.PackageID & """ and
cc.ContentID is null")
for each i in WMI.ExecQuery("select pc.* from SMS_PackageToContent pc where
pc.PackageID=""" & Pkg.PackageID & """")
case "window": if IsObject(logWindow) then
logWindow.document.all.logLines.innerHtml = logWindow.document.all.logLines.innerHTML & msg &
"<br/>"
set logWindow = WScript.CreateObject("InternetExplorer.Application",
"Log_")
.document.body.innerHTML = "<div id=""logLines""
style='"font:10pt sans-serif;text-align:left;"" />"
' provide a way of terminating the script in windowed mode - closing the log window will
terminate the script
Make all of these lines 1 line long with proper spacing and the script will run fine.
Hi Scott
Thank you very much for your help. It's now working :)
Just a small correction:
original:
should be:
.document.body.innerHTML = "<div id=""logLines"" style=""font:10pt sans-serif;text-align:left;"" />"
"" instead of '" after style=
JBAB
Would it be possible to either attach a copy of this script on here or email it to me? I can't get mine to work and I think it is because of extra line breaks that the web page is putting in it.
Thanks all. I have updated the post with the locations where line breaks were added in the script. Please removed the line breaks and the script should work. I've also fixed the quotation problem mentioned by JBAB.
Great blog, great script. Just tried out the script and it worked fine after deleting all occurrences of [CR]
Can this be run as a scheduled task? It seems like the logging causes an IE window to stay running after the script executes.
This is a great post. I have an issue with using an ADR to deploy SCEP defs. You mentioned that if we use an ADR the expired content is removed from the update group which I can see. It does not remove it from the package source however. Is there any way to do this?
@ Sandy - The script provided here does the source cleanup (step 8 in the blog).
Excellent content - thanks!
excellent thing, it works both in CM 2012 and in 2007
Thanks for the script. I'm having a problem - When I run it, I get a window that says
Line: 21
Char: 1
Error: Not supported
Code: 8004100C
Source: SWbemObjectEx
any ideas?
Thanks for any help,
Steve
Script doesnt work for me ether. Fails on line 19. Can you post somewhere? You have no idea how bad I want to clean up my source directories. Its a shame that the product doesn't do this btw.