Today I was installing a new ConfigMgr distribution point on a remote site and wanted to start distributing packages to it. But since the network bandwidth is only 4Mbps to that site, rather than distribute all the required packages in one go I wanted to distribute groups of packages out of working hours when the network is more free and no-one is affected. Yes you can use bandwidth throttling on the distribution point, but I prefer to schedule my distributions out of hours so I don’t need to.
But to find out what time-frame is required to distribute my packages, I need to get an idea of how long a package distribution will take. To do this, I use a script I published in an earlier blog (actually an updated version I haven’t published yet!) that will allow me to schedule and monitor a package distribution in SCCM, and will send me an email when the distribution is complete telling me how big the package is and how long the distribution took. This will enable me to get a rough calculation of the amount of time needed to distribute packages, assuming I know the volume of data I need to distribute.
So based on the time taken by this boot image, I can distribute 300MB in 20 minutes, so just less than 1GB per hour. Of course, several factors can affect that time, but it gives a rough guide which is useful enough.
But now how do I find out the volume of data I need to distribute? Well, lets start with the Application content packages. I like to organise my Applications into subfolders in the SCCM console, and I have some scripts that will distribute all the packages in the console folder I choose.
Let’s choose the ‘Non-Default Apps’ folder. This contains Applications that are not part of our default deployment image. How do I find out the total size of all the content files in each application in that folder?
Ok, well let’s use some PowerShell 🙂 First, I need to get the ID of that console folder.
$server = "sccmsrv-01" $SiteCode = "ABC" $FolderName = "Non-Default Apps" # Applications\Non-Default Apps # Get FolderID Write-Host "Getting FolderID of Console Folder '$FolderName'" $FolderID = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerNode where Name='$FolderName'" | Select ContainerNodeID $FolderID = $FolderID.ContainerNodeID Write-host " $FolderID"
I have entered my SCCM site server name, site code, and the name of the console folder as variables so the script is portable. Then I query the WMI on the site server to get the ContainerNodeID of the console folder.
Now I can query for all Applications in that folder using the FolderID, and get the unique ID, or ‘InstanceKey’ of each one, which equates to the CI Unique ID value for the Application in the SCCM console. You can add that value in the console from the available fields.
# Get InstanceKey of Folder Members Write-Host "Getting Members of Folder" $FolderMembers = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerItem where ContainerNodeID='$FolderID'" | Select InstanceKey $FolderMembers = $FolderMembers.InstanceKey write-host " Found $($FolderMembers.Count) applications"
Cool, it’s found 21 Applications in that folder. Now we need to translate the Unique ID of each Application to its friendly name. There’s more than one way to do that, but I chose to use the SMS_ObjectName class:
# Get Application name of each Folder member write-host "Getting Application Names" $NameList = @() foreach ($foldermember in $foldermembers) { $Name = Get-wmiobject -Namespace "ROOT\SMS\Site_$SiteCode" -Query "select Name from SMS_ObjectName where ObjectKey='$foldermember'" | Select -ExpandProperty Name $NameList += $Name } $namelist = $NameList | sort
Now that we have the list of Applications we need to find out the size of the content files in the deployment types for each Application. The slightly annoying thing is that this information is serialized into xml format and stored in a property called SDMPackageXML, so we need to deserialize that property.
First we will import a .Net assembly and add an accelerator to perform deserialization.
# import assemblies [System.Reflection.Assembly]::LoadFrom(“C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.dll”) | Out-Null # Creating Type Accelerators $accelerators = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators') $accelerators::Add('SccmSerializer',[type]'Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer')
Now we can loop through each Application, deserialize the SDMPackageXML property, loop through each deployment type that might be present, and retrieve the size of each file in the contents of each deployment type, adding the size values together as we go and storing them in a variable.
# Deserialize each SDMPackageXML property, and get the file size of each file in the contents for each deployment type $totalsize = 0 foreach ($name in $namelist) { write-host " Deserializing $name" $app = [wmi](gwmi -ComputerName $server -Namespace root\sms\site_$code -class sms_application | ?{$_.LocalizedDisplayName -eq $Name -and $_.IsLatest -eq $true}).__Path $appXML = [SccmSerializer]::DeserializeFromString($app.SDMPackageXML,$true) $DTs = $appxml.DeploymentTypes foreach ($DT in $DTs) { $sizes = $dt.Installer.Contents.Files.Size foreach ($size in $sizes) {$totalsize = $totalsize + $size} } }
Now let’s output the results:
write-host "Total Size of all content files for every application in the '$FolderName' folder is:" -ForegroundColor Green write-host "$(($totalsize / 1GB).ToString(".00")) GB" -ForegroundColor Green
So based on my initial test, it could take around 15 hours to distribute 13.4 GB of data! Wow, better schedule it for the weekend!
Here is the full script:
<# This script gets the total size of all the content files for each deployment type for each application in the console folder you specify. #> $server = "sccmsrv-01" $SiteCode = "ABC" $FolderName = "Non-Default Apps" # Applications\Default Apps # import assemblies [System.Reflection.Assembly]::LoadFrom(“C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.dll”) | Out-Null # Creating Type Accelerators $accelerators = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators') $accelerators::Add('SccmSerializer',[type]'Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer') # Get FolderID Write-Host "Getting FolderID of Console Folder '$FolderName'" $FolderID = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerNode where Name='$FolderName'" | Select ContainerNodeID $FolderID = $FolderID.ContainerNodeID Write-host " $FolderID" # Get InstanceKey of Folder Members Write-Host "Getting Members of Folder" $FolderMembers = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerItem where ContainerNodeID='$FolderID'" | Select * | Select InstanceKey $FolderMembers = $FolderMembers.InstanceKey write-host " Found $($FolderMembers.Count) applications" # Get Application name of each Folder member write-host "Getting Application Names" $NameList = @() foreach ($foldermember in $foldermembers) { $Name = Get-wmiobject -Namespace "ROOT\SMS\Site_$SiteCode" -Query "select Name from SMS_ObjectName where ObjectKey='$foldermember'" | Select -ExpandProperty Name $NameList += $Name } $namelist = $NameList | sort # Deserialize each SDMPackageXML property, and get the file size of each file in the contents for each deployment type $totalsize = 0 foreach ($name in $namelist) { write-host " Deserializing $name" $app = [wmi](gwmi -ComputerName $server -Namespace root\sms\site_$code -class sms_application | ?{$_.LocalizedDisplayName -eq $Name -and $_.IsLatest -eq $true}).__Path $appXML = [SccmSerializer]::DeserializeFromString($app.SDMPackageXML,$true) $DTs = $appxml.DeploymentTypes foreach ($DT in $DTs) { $sizes = $dt.Installer.Contents.Files.Size foreach ($size in $sizes) {$totalsize = $totalsize + $size} } } write-host "Total Size of all content files for every application in the '$FolderName' folder is:" -ForegroundColor Green write-host "$(($totalsize / 1GB).ToString(".00")) GB" -ForegroundColor Green
And here is a script I use to distribute all the packages in a console folder to a single distribution point:
$DP = "sccmsrvdp-02v.contoso.com" $SiteCode = "ABC" $FolderName = "Default Apps" # Applications\Default Apps Write-Host "Getting FolderID of Console Folder '$FolderName'" $FolderID = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerNode where Name='$FolderName'" | Select ContainerNodeID $FolderID = $FolderID.ContainerNodeID Write-host " $FolderID" Write-Host "Getting Members of Folder" $FolderMembers = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_ObjectContainerItem where ContainerNodeID='$FolderID'" | Select InstanceKey $FolderMembers = $FolderMembers.InstanceKey write-host "Getting App Names" foreach ($Folder in $FolderMembers) { $App = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_Application where ModelName='$Folder'" | Select LocalizedDisplayName $App = $App.LocalizedDisplayName Write-host "Distributing content for $App to $DP" Start-CMContentDistribution -ApplicationName $App -DistributionPointName $DP }
You can distribute any kind of package this way, for example to distribute driver packages instead, query the SMS_DriverPackage class. Replace the last section of the script with:
write-host "Getting DriverPack Names" foreach ($Folder in $FolderMembers) { $DriverPack = Get-WmiObject -Namespace "ROOT\SMS\Site_$SiteCode" ` -Query "select * from SMS_DriverPackage where PackageID='$Folder'" | Select Name $DriverPack = $DriverPack.Name Write-host "Distributing content for $DriverPack to $DP" Start-CMContentDistribution -DriverPackageName $DriverPack -DistributionPointName $DP }