Calculate the Total Size of all Packages in a ConfigMgr Task Sequence

Today I got an interesting question – Is it possible to gather the sizes for all packages referred by a OS deployment task sequence?  I have a script that will find all package sizes for any group of packages you select, but not specifically for a task sequence.

But I found it can be done quite easily.  Simply enter your task sequence name and your site code, and the script will return all the packages referenced in that task sequence with their package sizes, and also give the total count and total size.

Capture

 


$TaskSequenceName = "Windows OS Deployment x64"
$SiteCode = "ABC"

$TSID = Get-WmiObject -Namespace ROOT\sms\Site_$SiteCode -Query "Select PackageID from SMS_PackageStatusDetailSummarizer where Name = '$TaskSequenceName'" |
    Select -ExpandProperty PackageID

$PKGs = Get-WmiObject -Namespace ROOT\sms\Site_$SiteCode -Query "Select * from SMS_TaskSequencePackageReference where PackageID = '$TSID'" | 
    Select @{N='PackageName';E={$_.ObjectName}},@{N='Size (MB)';E={$($_.SourceSize / 1KB).ToString(".00")}} | Sort PackageName

$Stats = $PKGs | Measure-Object "Size (MB)" -sum
$PKGs | Out-GridView -Title "Packages in ""$TaskSequenceName""   |   Total Packages: $($Stats.Count)   |   Total Size of Packages: $(($Stats.Sum / 1KB).ToString(".00")) GB"

Checking for Failed Package Distributions in ConfigMgr with PowerShell

Recently when I checked the status of my Content distributions in the Monitoring node of the ConfigMgr console, I noticed some where the compliance was not reporting as 100%.  This means there was a failure in the distribution which needs to be addressed, usually by redistributing. However, ConfigMgr does not notify you of such distributions failures, which means you have to check in the console, or the status messages or the log files to find out.  It will retry a distribution up to 100 times, but after that I guess it just remains in a failed state.

So I wrote a Powershell script that will check for failed distributions, and give me various info such as the package name, type and ID, the distribution point where the failure occurred and the reason for the failure.  It can be run in a PS console window, but can also send results by email, which makes it useful to be run regularly as a scheduled task, so you can monitor for distribution failures and take the necessary action to remedy them.

Here’s an example:

1

After running the script I can see that there are some distributions where the content hash failed to be validated, not an uncommon error.  A redistribution should fix that.  There is also an OS Image package that looks like it couldn’t talk to the distribution point, and given the date it’s given up trying.

If I choose the -SendEmail switch, it will send me a nice (?) neon-green HTML report 🙂

2

Even though it is designed for failed distributions, it will also report on distributions that are active, basically anything not yet in the successful state, so you could use it to view the progress of current distributions too.

For example, this time using the -GridView switch, I can see some Software Update Packages that are currently being distributed, and some that failed.  Since the failures are very recent, ConfigMgr will try to redistribute those packages.  If they remain in the failed state over time, then it needs investigating.

4

Configure the Script

All parameters in the script are non-mandatory, so set the defaults if you don’t want to enter them each time:

$SiteCode – Your ConfigMgr Site Code

$SiteServer – Your ConfigMgr Site Server

$From – the From email address

$To – the To email address

$Smtpserver – Your Smtp server name

$Subject – The email subject

The Script

<#

.SYNOPSIS
    Checks for failed or incomplete package distributions in ConfigMgr

.DESCRIPTION
    This script checks for any package distributions that are not in the successful state, and return the results either into the console window or by email in an HTML table.
    The following fields are returned:
    - Package Name
    - PackageID
    - Package Type
    - Distribution Point Name
    - Distribution State
    - Status Message
    - Last Update Time
    It can be run as a scheduled task to monitor for failed distributions.  Data is retrieved from WMI on the Site Server.

.PARAMETER SiteCode
    Your ConfigMgr SiteCode

.PARAMETER SiteServer
    Your ConfigMgr Site Server

.PARAMETER SendEmail
    Use this switch to send results by email.  Console output is the default.

.PARAMETER GridView
    Use this switch to output results to Powershell's GridView. Console output is the default.

.PARAMETER From
    If sending email, the From address

.PARAMETER To
    If sending email, the To address

.PARAMETER SmtpServer
    If sending email, the Smtp server name

.PARAMETER Subject
    If sending email, the email subject

.EXAMPLE
    .\Get-CMFailedDistributions.ps1
    Checks for unsuccesful package distributions and returns the results to the console window.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SendEmail
    Checks for unsuccesful package distributions and sends the results by email using the default parameters.

.EXAMPLE
    .\Get-CMFailedDistributions.ps1 -SiteCode XYZ -SiteServer sccmserver02 -SendEmail -To bill.gates@microsoft.com
    Checks for unsuccesful package distributions and sends the results by email using specified parameters.

.NOTES
    Script name: Get-CMFailedDistributions.ps1
    Author:      Trevor Jones
    Contact:     @trevor_smsagent
    DateCreated: 2015-05-12
    Link:        https://smsagent.wordpress.com
    Credits:     MessageIDs from Dan Richings http://www.danrichings.com/?p=166

#>

[CmdletBinding()]
    param
        (
        [Parameter(Mandatory=$False)]
            [string]$SiteCode = "ABC",
        [Parameter(Mandatory=$False)]
            [string]$SiteServer = "sccmserver-01",
        [parameter(Mandatory=$False)]
            [switch]$SendEmail,
        [parameter(Mandatory=$False)]
            [switch]$GridView,
        [parameter(Mandatory=$False)]
            [string]$From = "ConfigMgr@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$To = "ConfigMgr_Admins@contoso.com",
        [parameter(Mandatory=$False)]
            [string]$SmtpServer = "mysmtpserver",
        [parameter(Mandatory=$False)]
            [string]$Subject = "ConfigMgr Failed Distributions"
        )

#region Functions
function New-Table (
$Title,
$Topic1,
$Topic2,
$Topic3,
$Topic4,
$Topic5,
$Topic6,
$Topic7
)
    {
       Add-Content $msgfile "<style>table {border-collapse: collapse;font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "h2 {font-family: ""Trebuchet MS"", Arial, Helvetica, sans-serif;}"
       Add-Content $msgfile "th, td {font-size: 1em;border: 1px solid #1FE093;padding: 3px 7px 2px 7px;}"
       Add-Content $msgfile "th {font-size: 1.2em;text-align: left;padding-top: 5px;padding-bottom: 4px;background-color: #1FE093;color: #ffffff;}</style>"
       Add-Content $msgfile "<h2>$Title</h2>"
       Add-Content $msgfile "<p><table>"
       Add-Content $msgfile "<tr><th>$Topic1</th><th>$Topic2</th><th>$Topic3</th><th>$Topic4</th><th>$Topic5</th><th>$Topic6</th><th>$Topic7</th></tr>"
    }
function New-TableRow (
$col1,
$col2,
$col3,
$col4,
$col5,
$col6,
$col7
)
    {
        Add-Content $msgfile "<tr><td>$col1</td><td>$col2</td><td>$col3</td><td>$col4</td><td>$col5</td><td>$col6</td><td>$col7</td></tr>"
    }
function New-TableEnd {
    Add-Content $msgfile "</table></p>"}
#endregion

# Set WQL Query
$packages = @()
$Query = "
Select PackageID,Name,MessageState,ObjectTypeID,MessageID,LastUpdateDate
from SMS_DistributionDPStatus
where MessageState<>1
order by LastUpdateDate Desc
"

# Get unsuccessful distributions
$Distributions = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query $Query
foreach ($Distribution in $Distributions)
    {
        # Translate Package Type
        if ($Distribution.ObjectTypeID -eq "2")
            {$Type = "Standard Package"}
        if ($Distribution.ObjectTypeID -eq "14")
            {$Type = "OS Install Package"}
        if ($Distribution.ObjectTypeID -eq "18")
            {$Type = "OS Image Package"}
        if ($Distribution.ObjectTypeID -eq "19")
            {$Type = "Boot Image Package"}
        if ($Distribution.ObjectTypeID -eq "21")
            {$Type = "Device Setting Package"}
        if ($Distribution.ObjectTypeID -eq "23")
            {$Type = "Driver Package"}
        if ($Distribution.ObjectTypeID -eq "24")
            {$Type = "Software Updates Package"}
        if ($Distribution.ObjectTypeID -eq "31")
            {$Type = "Application Content Package"}

        # Translate Distribution State
        if ($Distribution.MessageState -eq "2")
            {$Status = "In Progress"}
        if ($Distribution.MessageState -eq "3")
            {$Status = "Error"}
        if ($Distribution.MessageState -eq "4")
            {$Status = "Failed"}

        # Translate Common MessageIDs
        if ($Distribution.MessageID -eq "2303")
            {$MSGID = "Content was successfully refreshed"}
        if ($Distribution.MessageID -eq "2324")
            {$MSGID = "Failed to access or create the content share"}
        if ($Distribution.MessageID -eq "2330")
            {$MSGID = "Content was distributed to distribution point"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Content hash has been successfully verified"}
        if ($Distribution.MessageID -eq "2323")
            {$MSGID = "Failed to initialize NAL"}
        if ($Distribution.MessageID -eq "2354")
            {$MSGID = "Failed to validate content status file"}
        if ($Distribution.MessageID -eq "2357")
            {$MSGID = "Content transfer manager was instructed to send content to the distribution point"}
        if ($Distribution.MessageID -eq "2360")
            {$MSGID = "Status message 2360 unknown"}
        if ($Distribution.MessageID -eq "2370")
            {$MSGID = "Failed to install distribution point"}
        if ($Distribution.MessageID -eq "2371")
            {$MSGID = "Waiting for prestaged content"}
        if ($Distribution.MessageID -eq "2372")
            {$MSGID = "Waiting for content"}
        if ($Distribution.MessageID -eq "2375")
            {$MSGID = "Created virtual directories on the defined share or volume on the distribution point succesfully"}
        if ($Distribution.MessageID -eq "2380")
            {$MSGID = "Content evaluation has started"}
        if ($Distribution.MessageID -eq "2381")
            {$MSGID = "An evaluation task is running. Content was added to the queue"}
        if ($Distribution.MessageID -eq "2382")
            {$MSGID = "Content hash is invalid"}
        if ($Distribution.MessageID -eq "2383")
            {$MSGID = "Failed to validate the package on the distribution point. The package may not be present or may be corrupt. Please redistribute it"}
        if ($Distribution.MessageID -eq "2384")
            {$MSGID = "Package has been successfully verified on the distribution point"}
        if ($Distribution.MessageID -eq "2388")
            {$MSGID = "Failed to retrieve the package list on the distribution point. Or the package list in content library doesn't match the one in WMI. Review smsdpmon.log for more information about this failure."}
        if ($Distribution.MessageID -eq "2391")
            {$MSGID = "Failed to connect to remote distribution point"}
        if ($Distribution.MessageID -eq "2397")
            {$MSGID = "Detail will be available after the server finishes processing the messages."}
        if ($Distribution.MessageID -eq "2398")
            {$MSGID = "Content Status not found"}
        if ($Distribution.MessageID -eq "2399")
            {$MSGID = "Successfully completed the installation or upgrade of the distribution point"}
        if ($Distribution.MessageID -eq "8203")
            {$MSGID = "Failed to update package"}
        if ($Distribution.MessageID -eq "8204")
            {$MSGID = "Content is being distributed to the distribution point"}
        if ($Distribution.MessageID -eq "8211")
            {$MSGID = "Package Transfer Manager failed to update the package on the distribution point. Review PkgXferMgr.log for more information about this failure."}

        # Get / Set Additional info
        $PKGID = $Distribution.PackageID
        $LastUPDTime = [System.Management.ManagementDateTimeconverter]::ToDateTime($Distribution.LastUpdateDate)
        $PKG = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -Query "Select * from SMS_PackageBaseclass where PackageID = '$PKGID'"

        # Add to a PS object
        $Package = New-Object psobject
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Name" -Value $PKG.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "PackageID" -Value $Distribution.PackageID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Package Type" -Value $Type
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution Point Name" -Value $Distribution.Name
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Distribution State" -Value $Status
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Status Message" -Value $MSGID
        Add-Member -InputObject $Package -MemberType NoteProperty -Name "Last Update Time" -Value $LastUPDTime
        $packages += $package
    }

if ($SendEmail)
    {
        # Create Tempfile to store data
        $msgfile = "$env:TEMP\mailmessage.txt"
        New-Item $msgfile -ItemType file -Force | Out-Null

        # Populate HTML table
        if ($packages -ne $null ) {
            $Params = @{
                Title = "ConfigMgr Failed Package Distributions"
                Topic1 = "Package Name"
                Topic2 = "PackageID"
                Topic3 = "Package Type"
                Topic4 = "Distribution Point Name"
                Topic5 = "Distribution State"
                Topic6 = "Status Message"
                Topic7 = "Last Update Time"
                }
        New-Table @Params
        foreach ($item in $packages )
            {
                $params = @{
                col1 = $item.'Package Name'
                col2 = $item.PackageID
                col3 = $item.'Package Type'
                col4 = $item.'Distribution Point Name'
                col5 = $item.'Distribution State'
                col6 = $item.'Status Message'
                col7 = $item.'Last Update Time'
                }
                New-TableRow @Params
            }
        New-TableEnd

        # Send email or return results
        $mailbody = Get-Content $msgfile
        Send-MailMessage -From $From -To $To -SmtpServer $SmtpServer -Subject $Subject -Body "$mailbody" -BodyAsHtml 

        # Delete tempfile
        Remove-Item $msgfile
        }
        Else {Write-Warning "No results were returned!"}
    }

if ($GridView)
    {
        if ($packages -ne $null)
            {$packages | Out-GridView -Title "ConfigMgr Failed Package Distributions"}
        Else {Write-Warning "No results were returned!"}
    }

If (!$GridView -and !$SendEmail)
    {
        if ($packages -ne $null)
            {$packages | ft -AutoSize}
        Else {Write-Warning "No results were returned!"}
    }

Calculate the Size of Multiple Packages in ConfigMgr with PowerShell

Have you ever wanted to select a group of applications or packages in ConfigMgr and find out the total size of the content files for all those packages?  Or maybe you have your packages organised into folders in the ConfigMgr console, and you want to find out the total content size of all the packages in that folder?  Well with PowerShell you can!

I wrote this script to help me in calculating the content size for multiple packages of any type I choose – applications, standard packages, driver packages, boot images etc, so that I know how much data I am sending across the wire when distributing packages to a new distribution point. If your network bandwidth to a distribution point is limited, then it is helpful to know how much data you are sending so you know whether you need to set rate limits, or distribute out of hours etc.

This script uses PowerShell’s handy Out-GridView to display the list of available packages for the package types found in the Software Library (you choose) and allow you to select multiple packages. It will then calculate the total size of all the packages you selected.  If you use the -Verbose option, it will also report the size of each individual package.

For Applications, Standard Packages and Driver Packages, which are often organised into sub-folders in the ConfigMgr console, you can also specify the folder name, and the script will return the total content size of all packages in that folder.

The following package types are supported:

  • Applications (Specific console folder supported)
  • Standard Packages (Specific console folder supported)
  • Driver Packages (Specific console folder supported)
  • Software Update Packages
  • OS Image Packages
  • Boot Image Packages

Get the script

Download from the Technet Gallery here.

Configure the script

The script has two parameters that you might want to set the defaults for, otherwise you will need to enter them each time:

  • $SiteServer – the name of the site server you are running the script on
  • $SiteCode – your Site code

Edit the following part of the script:


[Parameter(
Mandatory=$False,
HelpMessage="The Site Server name"
)]
[string]$SiteServer="mysccmserver",

[Parameter(
Mandatory=$False,
HelpMessage="The Site Code"
)]
[string]$SiteCode="ABC"

The script also has comment-based help.

Examples

Get the total content size of all Adobe Flash Player Applications

I run the script with the -Applications switch:

Get-CMSelectedSoftwareContentSizes.ps1 -Applications

Select the Adobe Flash Player applications and click OK:

1

Total content size is returned:

2

Get the total size of all Intel packages and also report the size of each package

I run the script with the -Packages and the -Verbose switches:

Get-CMSelectedSoftwareContentSizes.ps1 -Packages -Verbose

Select the Intel packages and click OK:

3

Total size and individual sizes are returned:

4

Get the total size of all Dell driver packages in the “Dell System Cab Driver Packages” console folder and report the size of each package

In the ConfigMgr console, I have Dell driver packages organised into a subfolder:

5

I run the script and use the -DriverPackages, -FolderName and -Verbose switches:

Get-CMSelectedSoftwareContentSizes.ps1 -DriverPackages -FolderName “Dell System Cab Driver Packages” -Verbose

The individual size and total size of all driver packages in that console folder is returned:

7

Get the Total Size of All Application Content Files in an SCCM Console Folder

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.

CaptureSo 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.

Capture

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.

Capture

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"

Capture

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

Capture

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
}

Deploying KB3025945 Using the ConfigMgr Package Model

We recently had some reports of IE9 crashing after installing a MS patch KB3008923 released December 9th, 2014.  A few days ago on January 22nd, 2015 MS released an update that fixes the issue, KB3025945.  Unfortunately at the time of writing, the update is not available on WSUS, so it must be manually downloaded and deployed.  I decided to deploy the patch using ConfigMgr to the affected machines.  Of course, you could use SCUP to deploy the patch using the Software Updates model, but I decided to use the standard Package and Program model instead, with some Powerhelp ;).  Here’s a step-by-step guide for deploying the patch.  I’m using ConfigMgr 2012 R2 CU3.

Create a Collection

First, we need to identify all the machines that may be affected by this issue.  According to the KB, it only affects machines that have the KB3008923 installed, Internet Explorer 9, and the following operating systems: Windows 7 SP1, Windows 2008 SP2 and Windows 2008 R2.

Let’s use a little Powershell to create the collection and add the query rule that will filter for affected machines.  I’m using a limiting collection called ‘All SCCM 2012 Clients’, and I’m using Software Inventory to determine the IE version installed and the Win32_QuickFixEngineering WMI class to determine installed patches, so make sure that’s all enabled in your client settings (Software Inventory, Hardware Inventory classes).


# Create the collection
New-CMDeviceCollection -LimitingCollectionName "All SCCM 2012 Clients" -Name "KB3025945" `
-Comment "Manual deployment of patch KB3025945 to all Win 7 SP1, W2K8 R2 SP1 and W2K8 SP2 machines with KB3008923 and IE9 installed" `
-RefreshType Periodic

# Add the query rule
Add-CMDeviceCollectionQueryMembershipRule -CollectionName "KB3025945" -QueryExpression `
"select SMS_R_System.Name, SMS_G_System_OPERATING_SYSTEM.Caption, SMS_G_System_OPERATING_SYSTEM.Version
from SMS_R_System
inner join SMS_G_System_QUICK_FIX_ENGINEERING on SMS_G_System_QUICK_FIX_ENGINEERING.ResourceID = SMS_R_System.ResourceId
inner join SMS_G_System_SoftwareFile on SMS_G_System_SoftwareFile.ResourceID = SMS_R_System.ResourceId
inner join SMS_G_System_OPERATING_SYSTEM on SMS_G_System_OPERATING_SYSTEM.ResourceID = SMS_R_System.ResourceId
where SMS_G_System_SoftwareFile.FilePath like ""%\\Program Files\\Internet Explorer\\""
and SMS_G_System_SoftwareFile.FileName like ""iexplore.exe""
and SMS_G_System_SoftwareFile.FileVersion like ""9.%"" and
SMS_G_System_QUICK_FIX_ENGINEERING.HotFixID = ""KB3008923"" and
SMS_G_System_OPERATING_SYSTEM.Caption in (
""Microsoft Windows 7 Enterprise"",
""Microsoft Windows 7 Enterprise K"",
""Microsoft Windows 7 Enterprise N"",
""Microsoft Windows 7 Professional"",
""Microsoft Windows 7 Ultimate"",
""Microsoft Windows Server 2008 R2 Enterprise"",
""Microsoft Windows Server 2008 R2 Standard"",
""Microsoft® Windows Server® 2008 Enterprise"",
""Microsoft® Windows Server® 2008 Standard""
)
and SMS_G_System_OPERATING_SYSTEM.Version in (""6.0.6002"",""6.1.7601"")" `
-RuleName "KB3008923 / IE9"

Download the Patches

Great, now we have the collection, let’s download the patches from the Microsoft Update Catalog and save them to our SCCM package source.  There are 5 patches available for the different OS’s, so I’m gonna download them all.

Capture

Create the Packages, Programs and Deployments with Powershell

Lets make life easy again and use the native ConfigMgr Powershell cmdlets to do the work for us.

First off, I’m setting the Package and Program property variables as these will be common to all the packages.  Be sure to add your distribution point group name.  Further down in the script, from line 36, we need to set the package source location ($source) for each of the packages we will create for the different OS’s.  If you are happy with the Package name ($Pkg) you can leave it as is, and the command-lines are populated for you.  Since the installers are msu files, we will use WUSA.exe to install them silently with the /quiet and /norestart switches.  We will deploy the packages to the collection we created, simply named ‘KB3025945’.

I’m choosing to create a Required deployment that will begin as soon as possible, and I’m suppressing any notifications as I prefer to deploy silently in most cases, but you can of course change these settings in the script.

Run the script, and it will create the packages, programs, deployments, and distribute the package content.


<#
----------------
Deploy KB3025945
----------------

This script creates packages and programs for KB3025945, distributes the packages and deploys them.

Set the variables below as required
#>
## Set Package / Program Properties

# Required amount of disk space
$DiskSpace = "100"
# Units for Disk space requirement, eg KB, MB or GB
$DiskUnit = "MB"
# Maximum allowed run time
$Duration = "16"
# Program can run, eg "WhetherOrNotUserIsLoggedOn", "OnlyWhenNoUserIsLoggedOn", "OnlyWhenUserIsLoggedOn"
$RunType = "WhetherOrNotUserIsLoggedOn"
# Run mode, eg "RunWithAdministrativeRights" or "RunWithUserRights"
$RunMode = "RunWithAdministrativeRights"
# Allow user to interact?
$UserInteraction = $False
# Allow program to be installed from task sequence?
$EnableTS = $True
# Distribution Point Group Name
$DPG = "All Distribution Points"
# Set OS versions as array
$OSes = "Win7","Win7x64","W2K8","W2K8R2","W2K8x64"
## Set error action
$ErrorActionPreference = "Stop"
foreach ($OS in $OSes)
{

# Set Package source location and file names
if ($OS -eq "Win7")
{
$Pkg = "KB3025945 - Windows 7 SP1 x86"
$Source = "\\sccmserver-01\SoftwareUpdates\Out-of-Band Patches\KB3025945\Update for Windows 7 (KB3025945)"
$CommandLine = "wusa.exe X86-all-ie9-windows6.1-kb3025945-x86_ba77aa242aa4a0991c8879f9a04aaedd4c4b67c0.msu /quiet /norestart"
}
if ($OS -eq "Win7x64")
{
$Pkg = "KB3025945 - Windows 7 SP1 x64"
$Source = "\\sccmserver-01\SoftwareUpdates\Out-of-Band Patches\KB3025945\Update for Windows 7 for x64-based Systems (KB3025945)"
$CommandLine = "wusa.exe AMD64-all-ie9-windows6.1-kb3025945-x64_6566d74c6f14e6d4b120d2b07d91711f2e4c7dc9.msu /quiet /norestart"
}
if ($OS -eq "W2K8")
{
$Pkg = "KB3025945 - Windows Server 2008 x86 SP2"
$Source = "\\sccmserver-01\SoftwareUpdates\Out-of-Band Patches\KB3025945\Update for Windows Server 2008 (KB3025945)"
$CommandLine = "wusa.exe X86-all-ie9-windows6.0-kb3025945-x86_4a6f65fe4b418deb3e9626ba86234c3db718f85f.msu /quiet /norestart"
}
if ($OS -eq "W2K8R2")
{
$Pkg = "KB3025945 - Windows Server 2008 R2 SP1"
$Source = "\\sccmserver-01\SoftwareUpdates\Out-of-Band Patches\KB3025945\Update for Windows Server 2008 R2 x64 Edition (KB3025945)"
$CommandLine = "wusa.exe AMD64-all-ie9-windows6.1-kb3025945-x64_6566d74c6f14e6d4b120d2b07d91711f2e4c7dc9.msu /quiet /norestart"
}
if ($OS -eq "W2K8x64")
{
$Pkg = "KB3025945 - Windows Server 2008 x64 SP2"
$Source = "\\sccmserver-01\SoftwareUpdates\Out-of-Band Patches\KB3025945\Update for Windows Server 2008 x64 Edition (KB3025945)"
$CommandLine = "wusa.exe AMD64-all-ie9-windows6.0-kb3025945-x64_5cb8756529a63243805301e4e26f375bb6819fc1.msu /quiet /norestart"
}

## Create the Package ##

write-host "Creating Package: $Pkg" -foregroundcolor Green
try
{
$Package = New-CMPackage -Name $Pkg -Path $Source
}
catch { write-host "Could not create new package.`n$($_.Exception.Message)" -ForegroundColor Red; break }

## Create a Program

write-host 'Adding Program'
try
{
$Program = New-CMProgram `
-PackageName $Pkg `
-StandardProgramName $Pkg `
-CommandLine $CommandLine `
-DiskSpaceRequirement $DiskSpace `
-DiskSpaceUnit $DiskUnit `
-Duration $Duration `
-ProgramRunType $RunType `
-RunMode $RunMode `
-UserInteraction $UserInteraction
}
catch { write-host "Could not add program.`n$($_.Exception.Message)" -ForegroundColor Red; break }

## Update Program

write-host 'Updating Program'
try
{
$ProgramUpdate = Set-CMProgram `
-Name $Pkg `
-ProgramName $Pkg `
-StandardProgram `
-EnableTaskSequence $EnableTS
}
catch { write-host "Could not update program.`n$($_.Exception.Message)" -ForegroundColor Red; break }

## Distribute Content to DPs ##

write-host 'Distributing Content to DPs'
try
{
Start-CMContentDistribution -PackageName $Pkg -DistributionPointGroupName $DPG
}
catch { write-host "Could not distribute package.`n$($_.Exception.Message)" -ForegroundColor Red; break }


## Deploy the package

write-host "Deploying the package...please wait"
try
{
Start-CMPackageDeployment -CollectionName "KB3025945" `
-PackageName $pkg `
-ProgramName $pkg `
-StandardProgram `
-DeployPurpose Required `
-FastNetworkOption DownloadContentFromDistributionPointAndRunLocally `
-SlowNetworkOption DownloadContentFromDistributionPointAndLocally `
-SoftwareInstallation $true `
-SystemRestart $false `
-ScheduleEvent AsSoonAsPossible `
-RerunBehavior RerunIfFailedPreviousAttempt
}
catch { write-host "Could not deploy the package.`n$($_.Exception.Message)" -ForegroundColor Red; break }


}
write-host 'Done!' -ForegroundColor Yellow

Set the OS Requirement on the Programs

Great, now I have everything done…well, nearly everything…

Capture2

Capture4

Capture5

A limitation of the native Powershell cmdlets is that we can’t set the OS requirement on the Programs, so we need to do that in the console.  In the Properties of each program, set the relevant OS:

Capture3

That’s it!  Installation logging will appear in the WindowsUpdate.log on the client.  Since we did not enforce a reboot the clients will install the patch, but it will remain in the ‘reboot pending’ state until a reboot has been performed.  You can monitor your deployment using the standard methods in the ConfigMgr console or the built-in reports.

Schedule and Monitor ConfigMgr Package Distributions with PowerShell

Update! (22-Nov-2014) Updated the script to include a prompt for which distribution point group to use and a couple of minor improvements

Have you ever wanted to have the capability of scheduling a package distribution in ConfigMgr to occur at a specific date / time? Or perhaps you want to monitor your package distributions an receive an email when it has completed? Well now you can!

As a ConfigMgr administrator, I sometimes want to schedule my package distributions to take place outside of office hours, especially with very large packages like windows images.  I don’t want to consume a lot of bandwidth during working hours, so I prefer to start them at a time when the bandwidth I consume has the least impact on the sites I am distributing from and to. Sure I can use bandwidth limiting on the distribution point, but I don’t want to restrict bandwidth for all my distributions during working hours, only the really big ones, so out of hours is a better option.

Additionally I want to monitor my distributions to see when they have completed and how long they take.  There is a nice tool in the ConfigMgr 2012 R2 toolkit called ‘Distribution Point Job Queue Manager’, but there is no facility to receive a notification when a distribution has completed. Instead you must continually refresh it, which I can’t do if I’m scheduling a distribution out of hours.

To meet this need, I prepared a PowerShell script available from the Technet Gallery that will do either of the following things:

  1. Schedule the distribution of a new ConfigMgr package (equivalent to ‘Distribute Content‘ in the ConfigMgr console), monitor it, and send an email when the distributions have finished.

  2. Schedule the update of a package content to a distribution point group (equivalent to ‘Update Distribution Points‘ in the ConfigMgr Console), monitor it, and send an email when the distributions have finished.

  3. Monitor an already-running distribution and send an email when the distributions have finished.

The script will work for any package type, eg standard package, driver package, task sequence packages, software update packages, application content packages, OS images, boot images, OS installer packages.

It works by creating a ‘scheduled job’ in PowerShell, which will start at the time you specify.  The scheduled job is a PowerShell script that will start the package distribution, monitor the status of the distributions in WMI, then send an email when all the active distributions for that package are complete.  It will also email you if a distribution fails, although ConfigMgr will retry a failed distribution up to 99 times, so it will continue to monitor it for completion.

Update Distribution Points Example

Let’s take a look at an example.   In this case I’ve updated the source files for a deployment type called ‘Uninstall Java‘ in my ‘Java‘ application, and I want to update the distribution points with the new content.

Let’s run the script.  The first thing it asks for is what I want to do.  If I’ve created a new package or application and I want to distribute it to my distribution point group, I choose option [1].  If I’ve updated source files for an existing package or application and I want to update the distribution points with the new content, I choose [2].  If I’ve already started a package distribution and I simply want to monitor it, I choose [3].  In this case, i’m choosing [2].

1

Now it asks me for an email address to send the distribution monitor email notifications to.

2

Next I can enter the date and time that I want the package distribution to start.  You can enter enter any date and/or time format that your regional settings will understand.  For example, I can enter 22:00 to start my distribution today at 10pm.  Or I can enter 25/12/14  15:00 to start my distribution at 3pm on Christmas day in the UK time zone.  Pretty sure that won’t impact the network!  If I want to distribute the package now, I simply hit Enter, and it will be scheduled in 3 minutes from now.

3

Next it asks me for the name of the package or application I want to distribute.  It will then check the ConfigMgr WMI to validate whether a package or application exists with that name, and what type of package it is.

4

Since it is an application I must also enter which deployment type I want to distribute content for:

5

Great, now my distribution job has been scheduled and will run at the set time.

6

Let’s see what email notifications we get.  First, we get a notification that the distribution has started:

email1

Sometimes a distribution to one or more of the distribution points may fail the first time.  If it does, ConfigMgr will retry it up to 99 more times before giving up.  The script will continue to monitor the distribution, but we will get a notification that one or more the distributions has failed, and will be retried.  This is good to know as the distribution will likely take longer than usual because of a failure.

email2

Once the distribution has completed on all distribution points, we get an email summarizing the distribution.

email3Package Distribution Monitor Example

Let’s look at another example.  in this case, I’ve already distributed content for a package called ‘Retain Corporate Client‘, and I want to monitor the distribution for its completion.

In this case, I’m asked similar questions to before, but then it will tell me what package distributions are currently active, and ask me which one I want to monitor.  There is only one at the moment, and I enter the Package ID that it gives me.  Then my monitor job is scheduled to run in 2 minutes from now.

7

I get much the same emails as before:

email4

email5

Configuring the Script

To run the script, you should enter the following email variables at the top of the script.  These will probably not change so you can just enter them one time and that’s it.

Capture

The script must also be run as administrator.  This is required to create scheduled jobs.  If you are not running it as administrator, you will be notified.

Enjoy 🙂

 

Get the Deployment Status of ConfigMgr Packages and Task Sequences with PowerShell

In my last post, we looked at how to report on the deployment status of ConfigMgr Applications using PowerShell.  Of course, you can get this information from the built-in ConfigMgr SSRS reports too, but our aim here is to report this data quickly by using PowerShell to query the ConfigMgr database directly.  It enables you to quickly check the deployment status at any time, or to put the data into a csv allowing us to create a custom Excel report, for example.

In this post, we will do the same, but using a different SQL query which will report on both Packages and Task Sequences.

This query will give us the targeted collection names, the targeted computer and user names, the acceptance status and times, and the delivery status and times.  In the case of a failed installation, it will report a failure code in the LastExecutionResult column.

As with the previous script, you can filter the results either in PowerShell’s Gridview, or Excel, to identify specific deployments, computers, timescales, delivery states etc.

Capture

To run the script you will need db_datareader access to your ConfigMgr SQL database, or you can enter the credentials of an SQL account that does in the script.

Enter the following variables:

  • $Name – The name of the ConfigMgr Package or Task Sequence
  • $CSV – Enter Yes to return the data into a csv file
  • $Grid – Enter Yes to return the data into PowerShell’s Gridview
  • $datasource – The name of the SQL Server and Instance hosting your ConfigMgr database
  • $database – Your ConfigMgr database name

<#

This script gets the deployment status of a ConfigMgr 2012 Package or Task Sequence

#>

$Name = "My ConfigMgr Package" # Enter Package or Task Sequence Name
$CSV = "No" # Output to CSV, Yes or No
$Grid = "Yes" # Out-Gridview, Yes or No
# Get Start Time
$startDTM = (Get-Date)

# Database info
$dataSource = “mysqlserver\INST_SCCM”
$database = “CM_ABC”

# Open a connection
cls
Write-host "Opening a connection to '$database' on '$dataSource'"
#Using windows authentication, or..
$connectionString = “Server=$dataSource;Database=$database;Integrated Security=SSPI;”
# Using SQL authentication
#$connectionString = "Server=$dataSource;Database=$database;uid=ConfigMgrDB_Read;pwd=Pa$$w0rd;Integrated Security=false"
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()

# Getting Package / TS deployment status
Write-host "Running query..."

$query = "
select PackageName as 'Package / Task Sequence',ai.AdvertisementID as 'DeploymentID',ai.CollectionName, Name0 as 'Computer Name', User_Name0 as 'User Name', LastAcceptanceMessageIDName, LastAcceptanceStateName, LastAcceptanceStatusTime, LastStatusMessageIDName, LastStateName, LastStatusTime, LastExecutionResult
from v_ClientAdvertisementStatus cas
inner join v_R_System sys on sys.ResourceID=cas.ResourceID
inner join v_AdvertisementInfo ai on ai.AdvertisementID=cas.AdvertisementID
where PackageName = '$Name' and LastStatusTime is not null ORDER BY LastStatusTime Desc
"
$command = $connection.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteReader()

$table = new-object “System.Data.DataTable”
$table.Load($result)
$Count = $table.Rows.Count

if ($CSV -eq "Yes")
{
$Date = Get-Date -Format HH-mm--dd-MMM-yy
$Path = "C:\Script_Files\SQLQuery-$Date.csv"
$table | Export-Csv -Path $Path
Invoke-Item -Path $Path
}
If ($Grid -eq "Yes")
{
$table | Out-GridView -Title "Deployment Status of '$Name' ($count machines)"
}
# Close the connection
$connection.Close()

# Get End Time
$endDTM = (Get-Date)

# Echo Time elapsed
"Elapsed Time: $(($endDTM-$startDTM).totalseconds) seconds"