We were running low on disk space on our DP’s and I noticed we had a lot of packages with the “Copy the content in this package to a package share on distribution points” check box checked on packages that didn’t need it. This, as you probably know, causes the package to have 2 copies on each DP. The only time we needed this checked was on a package that was either referenced by a task sequence or had a deployment that ran from DP. So, I wrote a script that will fix this, based on the switches you use.
The basic breakdown of the script is this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[CmdletBinding(SupportsShouldProcess=$true)] param( [parameter(Mandatory=$true,HelpMessage="Site server where the SMS Provider is installed")] [ValidateScript({Test-Connection -ComputerName $_ -Count 1 -Quiet})] [string]$Server, [parameter(Mandatory=$false,HelpMessage="Sets the Log Level, 1 - Informational, 2 - Warning, 3 - Error")] [Int32]$LogLevel=2, [parameter(Mandatory=$false,HelpMessage="Log File Directory")] [string]$LogFileDir='C:Temp', [parameter(Mandatory=$false,HelpMessage="Write results to CSV File")] [switch]$WriteCSV, [parameter(Mandatory=$false,HelpMessage="Check the box where needed")] [switch]$CheckContentBox, [parameter(Mandatory=$false,HelpMessage="Clear the box where needed")] [switch]$ClearContentBox, [parameter(Mandatory=$false,HelpMessage="Minimum Package Size to clear check box")] [int32]$MinPackageSize=0, [parameter(Mandatory=$false,HelpMessage="Maximum Package Size to check box")] [int32]$MaxPackageSize=[int32]::MaxValue ) import-module ($Env:SMS_ADMIN_UI_PATH.Substring(0,$Env:SMS_ADMIN_UI_PATH.Length-5) + 'ConfigurationManager.psd1') |
Here we define our parameters for the script and, in some cases, set the default values. We also import in the ConfigManager PSModule.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Function New-LogEntry { # Writes to the log file Param( [Parameter(Position=0,Mandatory=$true)] [STRING] $Entry, [Parameter(Position=1,Mandatory=$false)] [INT32] $type = 1, [Parameter(Position=2,Mandatory=$false)] [STRING] $component = $ScriptName) if ($type -ge $Script:LogLevel) { if ($Entry.Length -eq 0) { $Entry = 'N/A' } $TZOffset = ([TimeZoneInfo]::Local).BaseUTcOffset.TotalMinutes $TZOffset = "$(Get-Date -Format "HH:mm:ss.fff")+$(-$TZOffset)" $Entry = "<!--[LOG[{0}]LOG]!--><time=""{2}"" type="" date="" {3}""="" thread="" {4}""="" ""="" context="" {5}""="" component="" {1}""="">" -f $Entry, (Get-Date -Format "MM-dd-yyyy"), $TZOffset, $pid, $type, $component $Entry | Out-File $Script:LogFile -Append -Encoding ascii } } |
Next, we define the New-LogEntry function. This will right out the message to a log using a format that can be read with CMTrace.exe
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
$ScriptName = $MyInvocation.MyCommand.Name if(-not ($LogFileDir -match '\$')){$LogFileDir = "$LogFileDir"} $LogFile = $ScriptName -replace '(.*).ps1', '$1' $CSVFile = $LogFileDir + $LogFile + '.csv' $LogFile = $LogFileDir + $LogFile + '.log' if($WriteCSV){'"PackageID","Package Name","Description","Package Size (KB)","Remediated","Task Sequence","Run From DP","Content Checked"' | Out-File $CSVFile -Encoding ascii} $Site = $(Get-WmiObject -ComputerName $Server -Namespace root/SMS -Class SMS_ProviderLocation).SiteCode Set-Location "$($site):" New-LogEntry '-------------------------------------------------' 2 New-LogEntry "Starting" 2 New-LogEntry "Options:" 2 New-LogEntry "Server - $Server" 2 New-LogEntry "LogLevel - $LogLevel" 2 New-LogEntry "LogFileDir - $LogFileDir" 2 New-LogEntry "LogFile - $LogFile" 2 New-LogEntry "ClearContentBox - $ClearContentBox" 2 New-LogEntry "CheckContentBox - $CheckContentBox" 2 New-LogEntry "WriteCSV - $WriteCSV" 2 New-LogEntry "MinPackageSize - $MinPackageSize" 2 New-LogEntry "MaxPackageSize - $MaxPackageSize" 2 New-LogEntry "Site - $Site" 2 |
We finish up the initialization here by first setting the Log and CSV file names. We figure out the Site code, and change the location to the site code (so the ConfigurationManager PowerShell Cmdlets will work). Last, but not least, we log our settings. Now, let’s get to work!
1 2 3 4 5 6 7 8 9 10 11 |
#Get a list of packages associated with all task sequences New-LogEntry 'Getting Task Sequences' $TaskSequences = $(Get-CMTaskSequence).References | select -Property Package #Get Deployments where they are run from DP New-LogEntry 'Getting Deployments' $Deployments = (Get-WmiObject -ComputerName $Server -Namespace root/sms/site_$($Site) -Class SMS_Advertisement | foreach {if ($_.RemoteClientFlags -bxor 0x80) {$_.PackageID}}) #Get list of packages New-LogEntry 'Getting Packages' $Packages = Get-CMPackage |
First thing we do is get a list of the task sequences and a list of deployments. This will be used later to verify if a package has a deployment where the “Run from DP” option is set and/or if the package is referenced by a task sequence. While we’re at it, we also get a list of the packages.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
New-LogEntry 'Starting processing of packages' foreach ($Package in $Packages) { New-LogEntry "Checking $($Package.PackageID)" $TSSMatch = $false $PkgMatch = $false $Entry = $Package.PackageID.ToString() $Package.Get() foreach ($TaskSequence in $TaskSequences) { if ($TaskSequence.Package -eq $Package.PackageID) { New-LogEntry "Referenced by task sequence" $Entry = "$Entry is referenced by a task sequence" $TSSMatch = $true break } } foreach($Deployment in $Deployments) #Check to see if the package has a deployment with "Run from distribution point" { if ($Package.PackageID -eq $Deployment) { New-LogEntry "$Deployment Deployment Match" if ($Entry.Length -gt 8) {$Entry = "$Entry, and "} $Entry = "$Entry has a deployment that run's from DP" $PkgMatch =$true break } } $CopyContentChecked = $Package.PkgFlags -band 0x80 |
In this section, we start our foreach loop and log the package we are checking. We initialize some flags to indicate if the package is referenced by a Task Sequence or has a deployment with “Run from DP”. The $Entry variable will be a log entry later, so we start it with the Package ID. Then the next foreach loop, we look to see if the package is referenced by a task sequence, if it is, we set the TSSMatch to true and add to the $Entry variable. The next foreach loop, we look to see if the package has a deployment with “Run from DP” set, if it does, again, we set a variable, this time PKGMatch, to true and add to the $Entry variable. The last thing we do is note if the package has the CopyContent check box checked or not.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
if ($PkgMatch -or $TSSMatch) #Should have box checked { if (-not ($CopyContentChecked)) { $IsFixed = $false $ContentChecked = $false New-LogEntry "$Entry. It should have 'Copy content' checked. Package Size - $($Package.PackageSize)KB" 2 if(($CheckContentBox) -and ($Package.PackageSize -le $MaxPackageSize)) { New-LogEntry "Setting Checkbox..." 2 $IsFixed = $true $Package.PkgFlags = $Package.PkgFlags -bor 0x80 $Package.RefreshPkgSourceFlag = $true $package.put() } if($WriteCSV) { "`"" + $Package.PackageID + "`",`"" + $Package.Name + "`",`"" + $Package.Description + "`",`"" + $Package.PackageSize + "`",`"" + $IsFixed + "`",`"" + $TSSMatch + "`",`"" + $PkgMatch + "`",`"" + $ContentChecked + "`"" | Out-File $CSVFile -Append ascii } } } |
Now we’re ready to fix things. If either of the two conditions checked come back true, then we should have the check box checked. Now we check to see if the package has the check box checked or not. If it doesn’t, we log this. We then check to see if the switch was set to fix this and if the package is smaller than the MaxPackageSize, we will set the check box and redistribute. We note this so, if required, we can update the CSV file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
if (-not ($PkgMatch -or $TSSMatch)) #Should not have box checked { if($CopyContentChecked) { $IsFixed = $false $ContentChecked = $true if ($Entry.Length -le 8) { $Entry = "$Entry has no Run from DP deployments and is not referenced by a TS. " } New-LogEntry "$Entry. It should NOT have 'Copy content' checked. Package Size - $($Package.PackageSize)KB" 2 if (($ClearContentBox) -and ($Package.PackageSize -ge $MinPackageSize)) { New-LogEntry "Clearing Checkbox..." 2 $IsFixed = $true $Package.PkgFlags = $Package.PkgFlags -band 0xff7f $Package.RefreshPkgSourceFlag = $true $Package.put() } if($WriteCSV) { "`"" + $Package.PackageID + "`",`"" + $Package.Name + "`",`"" + $Package.Description + "`",`"" + $Package.PackageSize + "`",`"" + $IsFixed + "`",`"" + $TSSMatch + "`",`"" + $PkgMatch + "`",`"" + $ContentChecked + "`"" | Out-File $CSVFile -Append ascii } } } } New-LogEntry 'Finished script' 2 New-LogEntry '-------------------------------------------------' 2 |
The last part is basically the opposite of the last step, checking to see if the check box is checked when it shouldn’t be and fixing it if asked and the package is larger than the MinPackageSize. The last part is logging that we’re done!
This script is available in it’s entirety in my Repository section, found at the top. I hope you can use this script and welcome any comments, suggestions, or improvements!