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 🙂

 

Re-Triggering a ConfigMgr Application Install

Ok, so let’s say you’ve deployed an Application in ConfigMgr, but you’re testing the Application – making sure that it works, that it installs correctly.  If it does, great.  But then you make some changes on the target client and you want to install it again to make sure that it still works, without making any changes to the Application itself.

For testing installations, I usually make Available deployments so that I can run them again if they fail.  But if the Application has installed correctly and you want to run it again, you can’t do that from the Software Center, as the Application will only have the option to Uninstall.

I guess the long way is to remove it from the targeted collection, refresh the policy on the client, add it to the collection again, refresh the client policy again.

But surely there must be something quicker?  Indeed there is 🙂  Using WMI, you can simply call the Install method for the Application in the CCM_Application class.

Here’s how with PowerShell:


$ComputerName = "PC001"
$AppName = "Microsoft Office 2013 Pro"

$s = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $s -Argu $ComputerName,$AppName -ScriptBlock `
{
param ($ComputerName,$AppName)
write-host "Getting Parameters for '$AppName' on $ComputerName"
$App = Get-WmiObject -computername $ComputerName -Namespace "root\ccm\ClientSDK" -Class CCM_Application | where {$_.Name -like "$AppName"} | Select-Object Id, Revision, IsMachineTarget
$AppID = $App.Id
$AppRev = $App.Revision
$AppTarget = $App.IsMachineTarget
write-host $AppID, $AppRev, $AppTarget -ForegroundColor Yellow
write-host "Triggering Installation!" -ForegroundColor Green
([wmiclass]'ROOT\ccm\ClientSdk:CCM_Application').Install($AppID, $AppRev, $AppTarget, 0, 'Normal', $False) | Out-Null
}
Remove-PSSession $s

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"

How to Get the Deployment Status of ConfigMgr Applications with PowerShell

PowerShell and SQL server.  It’s a combination I’m liking more every day 🙂  In this post, I give you a PowerShell script that will query your ConfigMgr SQL server and return the deployment status of a ConfigMgr Application.

It will return data for each deployment of the application, giving you the names of the deployment types and the targeted collections, as well as the status of each computer that has received the deployment.  The SQL query will interpret the AppEnforcementState value to it’s friendly name to make it easier to determine the deployment status of each device.  Finally, we sort by the LastComplianceMessageTime, showing the most recently completed deployments at the top.

Results can be returned into PowerShell’s Gridview, or CSV format, where you can filter the results for a specific deployment type, targeted collection, deployment status, computer, or deployment compliance message time.

Capture

What about package and task sequence deployments, you say?  Watch this space 😉

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:

  • $ApplicationName – The name of the ConfigMgr Application
  • $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 Application

#>

$ApplicationName = "Cisco Webex Meeting Center" # Enter the Application Name
$CSV = "No" # Output to CSV, Yes or No
$Grid = "Yes" # Out-Gridview, Yes or No

# Database info

$dataSource = “mysqlserver\INST_SCCM”
$database = “CM_ABC”

# Get Start Time
$startDTM = (Get-Date)

# 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 Application Deployment Data
Write-host "Running query..."

$query = "
select distinct
aa.ApplicationName,
ae.AssignmentID,
aa.CollectionName as 'Target Collection',
ae.descript as 'Deployment Type Name',
s1.netbios_name0 as 'Computer Name',
ci2.LastComplianceMessageTime,
ae.AppEnforcementState,
case when ae.AppEnforcementState = 1000 then 'Success'
when ae.AppEnforcementState = 1001 then 'Already Compliant'
when ae.AppEnforcementState = 1002 then 'Simulate Success'
when ae.AppEnforcementState = 2000 then 'In Progress'
when ae.AppEnforcementState = 2001 then 'Waiting for Content'
when ae.AppEnforcementState = 2002 then 'Installing'
when ae.AppEnforcementState = 2003 then 'Restart to Continue'
when ae.AppEnforcementState = 2004 then 'Waiting for maintenance window'
when ae.AppEnforcementState = 2005 then 'Waiting for schedule'
when ae.AppEnforcementState = 2006 then 'Downloading dependent content'
when ae.AppEnforcementState = 2007 then 'Installing dependent content'
when ae.AppEnforcementState = 2008 then 'Restart to complete'
when ae.AppEnforcementState = 2009 then 'Content downloaded'
when ae.AppEnforcementState = 2010 then 'Waiting for update'
when ae.AppEnforcementState = 2011 then 'Waiting for user session reconnect'
when ae.AppEnforcementState = 2012 then 'Waiting for user logoff'
when ae.AppEnforcementState = 2013 then 'Waiting for user logon'
when ae.AppEnforcementState = 2014 then 'Waiting to install'
when ae.AppEnforcementState = 2015 then 'Waiting retry'
when ae.AppEnforcementState = 2016 then 'Waiting for presentation mode'
when ae.AppEnforcementState = 2017 then 'Waiting for Orchestration'
when ae.AppEnforcementState = 2018 then 'Waiting for network'
when ae.AppEnforcementState = 2019 then 'Pending App-V Virtual Environment'
when ae.AppEnforcementState = 2020 then 'Updating App-V Virtual Environment'
when ae.AppEnforcementState = 3000 then 'Requirements not met'
when ae.AppEnforcementState = 3001 then 'Host platform not applicable'
when ae.AppEnforcementState = 4000 then 'Unknown'
when ae.AppEnforcementState = 5000 then 'Deployment failed'
when ae.AppEnforcementState = 5001 then 'Evaluation failed'
when ae.AppEnforcementState = 5002 then 'Deployment failed'
when ae.AppEnforcementState = 5003 then 'Failed to locate content'
when ae.AppEnforcementState = 5004 then 'Dependency installation failed'
when ae.AppEnforcementState = 5005 then 'Failed to download dependent content'
when ae.AppEnforcementState = 5006 then 'Conflicts with another application deployment'
when ae.AppEnforcementState = 5007 then 'Waiting retry'
when ae.AppEnforcementState = 5008 then 'Failed to uninstall superseded deployment type'
when ae.AppEnforcementState = 5009 then 'Failed to download superseded deployment type'
when ae.AppEnforcementState = 5010 then 'Failed to updating App-V Virtual Environment'
End as 'State Message'
from v_R_System_Valid s1
join vAppDTDeploymentResultsPerClient ae on ae.ResourceID=s1.ResourceID
join v_CICurrentComplianceStatus ci2 on ci2.CI_ID=ae.CI_ID AND
ci2.ResourceID=s1.ResourceID
join v_ApplicationAssignment aa on ae.AssignmentID = aa.AssignmentID
where ae.AppEnforcementState is not null and aa.ApplicationName='$ApplicationName'
order by LastComplianceMessageTime 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 Application '$ApplicationName' ($count machines)"
}

# Close the connection
$connection.Close()

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

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

Re-running a ConfigMgr Task Sequence on Multiple Computers

Recently I deployed a very simple task sequence to all our laptop computers which installs a new WiFi profile.  However, on viewing the deployment reports I noticed a number of machines where the deployment was stuck in either the ‘running’ state, or ‘failed’.  Although the deployment is set to ‘Re-run if failed previous attempt’ it seems the task sequence will not re-run automatically, only if a new schedule is created.  I wanted a quick and easy way to simply trigger the deployment again on all the computers that needed it, so I modified a script I use for re-running a task sequence to run against all computers in a csv.

Here is the script.  You need PS remoting active in your environment.  Simply add the location of the csv that contains the computernames you want to re-run the deployment on (no header required), and the name of the task sequence, and the script will do the rest.  It’s a little crude at the moment, but it works!  After running this, my compliance figures for the deployment were immediately boosted, and only a handful of machines remained that required individual troubleshooting 🙂

$ComputerNames = Get-Content -Path C:\Script_Files\WiFi_Profile_Failures.csv
$TaskSequenceName = "WiFi Profile"

cls
foreach ($ComputerName in $ComputerNames)
{ 
# Test connectivity to the computer
if (Test-Connection -Quiet -Count 2 -ComputerName $ComputerName -ErrorAction SilentlyContinue)
{$Online = "Yes"}else{$Online = "No"}
if ($Online -eq "No")
{write-host "$ComputerName is not online!"}
if ($Online -eq "Yes")
{
 
$s = New-PSSession -ComputerName $ComputerName
Invoke-Command -Session $s -Argu $ComputerName,$TaskSequenceName -ScriptBlock `
{
param ($ComputerName,$TaskSequenceName)
write-host "Getting PackageID for '$TaskSequenceName' on $ComputerName"
 $PackageID = get-wmiobject -computername $ComputerName -query "SELECT * FROM CCM_SoftwareDistribution" -namespace "root\ccm\policy\machine\actualconfig" | where {$_.PKG_Name -like $TaskSequenceName} | Select PKG_PackageID
 $PackageID = $PackageID.PKG_PackageID
 write-host $PackageID -ForegroundColor Yellow
 write-host "Getting ScheduleID for '$TaskSequenceName' on $ComputerName"
 $ScheduleID = Get-WmiObject -computername $ComputerName -Namespace "root\ccm\scheduler" -Class ccm_scheduler_history | where {$_.ScheduleID -like "*$PackageID*"} | Select-Object ScheduleID 
 $ScheduleID = $ScheduleID.ScheduleID
 write-host $ScheduleID -ForegroundColor Yellow
 write-host "Getting AdvertisementID for '$TaskSequenceName' on $ComputerName"
 $AdvertisementID = get-wmiobject -computername $ComputerName -query "SELECT * FROM CCM_SoftwareDistribution" -namespace "root\ccm\policy\machine\actualconfig" | where {$_.PKG_Name -like $TaskSequenceName} | Select ADV_AdvertisementID
 $AdvertisementID = $AdvertisementID.ADV_AdvertisementID
 write-host $AdvertisementID -ForegroundColor Yellow
 write-host "Setting re-run behaviour"
 $a = get-wmiobject -computername $ComputerName -query "SELECT * FROM CCM_TaskSequence" -namespace "root\ccm\policy\machine\actualconfig" | Where {$_.ADV_AdvertisementID -like "*$AdvertisementID*"} 
 $a.ADV_RepeatRunBehavior='RerunAlways'
 $a.Put() | Out-Null
 write-host "Creating mandatory assignment"
 $a = get-wmiobject -computername $ComputerName -query "SELECT * FROM CCM_TaskSequence" -namespace "root\ccm\policy\machine\actualconfig" | Where {$_.ADV_AdvertisementID -like "*$AdvertisementID*"}
 $a.ADV_MandatoryAssignments=$True
 $a.Put() | Out-Null
 
 write-host "Triggering the schedule now!" -ForegroundColor Green
 Invoke-WmiMethod -ComputerName $ComputerName -Namespace ROOT\ccm -Class SMS_Client -Name TriggerSchedule -ArgumentList "$ScheduleID" | Out-Null
 }
 Remove-PSSession $s
 }
 }

Unlocking Locked Objects in the SCCM Console using Powershell

Have you ever come across a lovely message like this from the SCCM 2012 console?  In this case I wanted to open the properties of a package but somehow SCCM still has a handle on it that it won’t release.  This could be because someone else is currently modifying it, but in this case I only just created it so I know that’s not true.

console

The solution is to delete the record for the locked object in the SQL database.  You can do this from the SQL server, but hey, why not do it with PowerShell for a quicker fix?

This script will query the ConfgMgr SQL server database for locked objects, list them in a Grid, ask you which object you want to unlock, then delete the record from the database.

Note the script assumes you are using an account that has the necessary permissions in the SQL database.

First, we get the list of locked objects and put them into a grid view so we can filter and find the object we need in case there are more than one.

grid

If it’s something that you think was locked recently, you can sort by the ‘AssignmentTime’ column and find the latest one.  Or if you think it was done by a specific user account, you can sort by ‘AssignedUser’.

The first column contains the ID for that record in the database.  The script asks you to input this record ID, then it will delete the record:

ps

Don’t forget to add your SQL server and database name to the beginning of the script.


<#

This script unlocks an object that is locked in the SCCM console.

#>

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

# Open a connection
cls
Write-host "Opening a connection to '$database' on '$dataSource'"
$connectionString = “Server=$dataSource;Database=$database;Integrated Security=SSPI;”
$connection = New-Object System.Data.SqlClient.SqlConnection
$connection.ConnectionString = $connectionString
$connection.Open()

# Get the list of locked objects, output to Grid
Write-host "Getting the list of locked objects"
$query = "select * from SEDO_LockState where LockStateID <> 0"
$command = $connection.CreateCommand()
$command.CommandText = $query
$result = $command.ExecuteReader()

$table = new-object “System.Data.DataTable”
$table.Load($result)
$table | Out-GridView

# Prompt for the record ID to unlock
$ID = Read-Host ">>Enter the ID number of the record you want to unlock"

# Get the LockID of the object
Write-host "Getting the LockID of this object"
$query2 = "select LockID from SEDO_LockState where ID = '$($ID)'"
$command = $connection.CreateCommand()
$command.CommandText = $query2
$result = $command.ExecuteReader()

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

# Delete the object
Write-Host "Deleting the object record"
$query3 = "DELETE from SEDO_LockState where LockID = '$($table)'"
$command = $connection.CreateCommand()
$command.CommandText = $query3
$result = $command.ExecuteReader()

# Close the connection
$connection.Close()

More info here: http://myitforum.com/myitforumwp/2013/02/22/unlocking-configmgr-2012-objects/

SCCM Package Distribution Email Notification

In my last post, we looked at a way to monitor ConfigMgr Package distributions with PowerShell.  In this post, we’ll add an email notification to it.  Sometimes when you are distributing a large package to multiple distribution points, it’s handy to receive a notification that the distribution has been completed so you don’t have to keep checking the distribution status.

In this script, we do much the same as in the previous post, except we watch for a change in the distribution status in WMI to see when the distribution has completed.  Then we send an email.

Simply add your email details, sitecode, and the polling frequency into the script, then run it. Choose which active distribution you want to monitor, then you’ll get an email when the distribution to all the relevant distribution points has completed.

As I mentioned in my previous post, ConfigMgr does some post-processing before the actual distribution is started, so if you try to monitor a distribution immediately after you distributed it, you might see no results.  One way to check when it’s started is to search for ‘Created package transfer job to send package … to distribution point’ in the distmgr.log on the site server.

dp1


<#

This script monitors an active distribution that you choose and sends an email once the distribution to all DPs has completed.
Note: Run on the SCCM Site Server, and complete the variables first.

#>

## Complete the variables ##

$ToEmail = "trevor.jones@mycompany.com"
$FromEmail = "sccmsiteserver@mycompany.com"
$smtpServer = "emailserver.mycompany.com"
$SiteCode = "ABC"
# Poll frequency in seconds
$Seconds = "120"

## Start of script

Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
$Drive = $SiteCode + ':'
cd $Drive

$Djobs = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DistributionStatus -Filter "Type='2'" | Select Assets,PackageID

if ($djobs -eq $Null)
{
Write-host "There are no active distributions!" -ForegroundColor Yellow
Write-host "If you just distributed something, try again in a couple of minutes as the server may be processing the request."
write-host "Press any key"
$HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL
$HOST.UI.RawUI.Flushinputbuffer()
exit
}

Write-host ""
Write-host "The following distributions are active:"
write-host ""

foreach ($Djob in $Djobs)
{
$ID = $djob.PackageID
$assets = $djob.Assets
# Get PackageName and Type

$PKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_Package -Filter "PackageID='$ID'" | Select Name
If ($PKG -ne $null)
{
$Name = $PKG.Name
$Type = "Standard Package"
}

$BIPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_BootImagePackage -Filter "PackageID='$ID'" | Select Name
If ($BIPKG -ne $null)
{
$Name = $BIPKG.Name
$Type = "Boot Image Package"
}

$CNPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ContentPackage -Filter "PackageID='$ID'" | Select Name
If ($CNPKG -ne $null)
{
$Name = $CNPKG.Name
$Type = "Application Content Package"
}

$DRVPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DriverPackage -Filter "PackageID='$ID'" | Select Name
If ($DRVPKG -ne $null)
{
$Name = $DRVPKG.Name
$Type = "Driver Package"
}

$IMGPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ImagePackage -Filter "PackageID='$ID'" | Select Name
If ($IMGPKG -ne $null)
{
$Name = $IMGPKG.Name
$Type = "Image Package"
}

$SUPKG = Get-WmiObject -Namespace root\SMS\Site_D1G -Class SMS_SoftwareUpdatesPackage -Filter "PackageID='$ID'" | Select Name
If ($SUPKG -ne $null)
{
$Name = $SUPKG.Name
$Type = "Software Updates Package"
}

Write-host " Package ID: $ID"
write-host " Package Name: $name"
write-host " Package Type: $Type"
write-host " Number of DPs: $Assets"
write-host ""
}

$PKG2Monitor = Read-host "Enter the Package ID of the distrbution you want to monitor"

write-host " "
write-host "Monitoring the distribution status of package $PKG2Monitor on all DPs. An email will be sent when complete." -ForegroundColor Yellow
write-host "Do not close this PowerShell window!" -ForegroundColor Yellow

while ((Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DistributionStatus | where {($_.Type -eq "2" -or $_.Type -eq "4") -and $_.PackageID -eq "$PKG2Monitor"}).Type -eq '2' `
-or (Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DistributionStatus | where {($_.Type -eq "2" -or $_.Type -eq "4") -and $_.PackageID -eq "$PKG2Monitor"}).Type -eq '4')
{
Start-Sleep -Seconds $Seconds
}

if ($PKG)
{$Class = "SMS_Package"}
if ($TSPKG)
{$Class = "SMS_TaskSequencePackage"}
if ($BIPKG)
{$Class = "SMS_BootImagePackage"}
if ($CNPKG)
{$Class = "SMS_ContentPackage"}
if ($DRVPKG)
{$Class = "SMS_DriverPackage"}
if ($IMGPKG)
{$Class = "SMS_ImagePackage"}
if ($SUPKG)
{$Class = "SMS_SoftwareUpdatesPackage"}

$NName = Get-WmiObject -Namespace root\SMS\Site_D1G -Class $Class -Filter "PackageID='$PKG2Monitor'" | Select Name
$NName = $NName.Name

send-mailmessage -To $ToEmail -From $FromEmail -Subject "Package distribution completed for $NName ($PKG2Monitor)" -body "The package distribution for $NName ($PKG2Monitor) has completed on all DPs." -smtpServer $smtpServer

SCCM Package Distribution Monitor

If you’ve installed the ConfigMgr 2012 R2 Toolkit, you’ve probably used the handy “Distribution Point Job Queue Manager” to monitor your distributions.  But you can also monitor them with Powershell if you wish to.  Here’s a simple script that will do just that.  It will identify the package ID, name and type, the distribution points that are currently receiving the distribution, when the distribution started, the total size of the package, the remaining size to be distributed, and overall progress of each individual distribution.

Note that ConfigMgr does some post-processing before the actual distribution is started, so if you try to monitor a distribution immediately after you distributed it, you might see no results.  One way to check when it’s started is to search for ‘Created package transfer job to send package … to distribution point’ in the distmgr.log on the site server.  Thinking about it, that could probably be scripted too, but that’s for another day 🙂

dp


## Variables ##
#!!Enter the sitecode here!!
$SiteCode = "ABC"

## Start of script

Import-Module 'C:\Program Files (x86)\Microsoft Configuration Manager\AdminConsole\bin\ConfigurationManager.psd1'
$Drive = $SiteCode + ':'
cd $Drive

cls
$Djobs = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DistributionJob | Select PkgID,NALPath,StartTime,RemainingSize,TotalSize

if ($djobs -eq $Null)
{
Write-host "There are no active distributions!" -ForegroundColor Yellow
Write-host "If you just distributed something, try again in a couple of minutes as the server may be processing the request."
write-host "Press any key"
$HOST.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | OUT-NULL
$HOST.UI.RawUI.Flushinputbuffer()
exit
}

foreach ($Djob in $Djobs)
{
$ID = $djob.PkgID
# Get PackageName and Type

$PKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_Package -Filter "PackageID='$ID'" | Select Name
If ($PKG -ne $null)
{
$Name = $PKG.Name
$Type = "Standard Package"
}

$BIPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_BootImagePackage -Filter "PackageID='$ID'" | Select Name
If ($BIPKG -ne $null)
{
$Name = $BIPKG.Name
$Type = "Boot Image Package"
}

$CNPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ContentPackage -Filter "PackageID='$ID'" | Select Name
If ($CNPKG -ne $null)
{
$Name = $CNPKG.Name
$Type = "Application Content Package"
}

$DRVPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_DriverPackage -Filter "PackageID='$ID'" | Select Name
If ($DRVPKG -ne $null)
{
$Name = $DRVPKG.Name
$Type = "Driver Package"
}

$IMGPKG = Get-WmiObject -Namespace root\SMS\Site_$SiteCode -Class SMS_ImagePackage -Filter "PackageID='$ID'" | Select Name
If ($IMGPKG -ne $null)
{
$Name = $IMGPKG.Name
$Type = "Image Package"
}

$PkgID = $Djob.PkgID
$NALPath = $Djob.NALPath
if ($Djob.StartTime -ne $null)
{
$StartTime = [System.Management.ManagementDateTimeConverter]::ToDateTime($Djob.StartTime).DateTime
}
$RemainingSize = $Djob.RemainingSize
$TotalSize = $Djob.TotalSize
if ($RemainingSize -ne $Null)
{
$percentComplete = "{0:N0}" -f (100-$RemainingSize/$TotalSize*100) + " %"
}
Else
{
$percentComplete = "Not calculated yet"
}
$RemainingSize = "{0:N0}" -f ($RemainingSize / 1MB) + " MB"
$TotalSize = "{0:N0}" -f ($TotalSize / 1MB) + " MB"

write-host ""
write-host "Package Name: $Name" -ForegroundColor Cyan
write-host "Packge Type: $Type"
Write-host "Package ID: $PkgID"
Write-host "Distribution Point: $NALPath"
Write-host "Start Time: $StartTime"
write-host "Remaining Size: $RemainingSize"
write-host "Total Size: $TotalSize"
Write-host "Percent Complete: $percentComplete"
}

An Alternative to Application Supersedence in SCCM

For some software applications, it’s inevitable that you’ll need to deploy a new version now and then.  For example, you deploy an application, then a new version gets released and you want to upgrade all your clients.  Application supersedence in SCCM2012 is a useful way to deploy a new version of an application – you create the new application and specify supercedence of the old version.  You can also uninstall the old version if required.  But for some simple applications it seems overkill to have to create a new application with every release, especially as you have to create deployment types, add deployments, maybe update your task sequences to include the new application, retire the old application etc.

Is there a better way?  Well, for apps that don’t require you to uninstall the old version first, I think there is.  Simply add new deployment types to your existing application. Create a new deployment type with the new version number in the existing application.  Then create a ‘dummy’ requirement rule on the old deployment type to something it’s certain to fail during evaluation. This will effectively disable it, yet keep it should you need it in future.  And then increase the priority of the new deployment type to 1 so it will evaluate and install first.

An example with Adobe Flash (ok, I’m not using 3rd Party Update management yet!)

Deploy1

A ‘dummy’ requirement rule for the previous version, eg a WMI query for the computer manufacturer that will fail the evaluation:

Require1

There are several benefits:

  • You can keep the previous application versions (deployment types) for as long as you want, should you need them
  • You don’t need to create new applications for every new version
  • You don’t need to update your applications in your task sequences
  • You don’t even need to distribute the new content manually, as it will get distributed automatically after you created the new deployment type
  • You don’t need to create a new deployment, as it will use the existing application deployment
  • If you have a ‘required’ deployment, then clients will update themselves with the new version after the next Application Deployment Evaluation cycle
  • You can name your Applications more generically, eg ‘My Application’, instead of ‘My Application V1’, ‘My Application v2’ etc
  • It can save a lot of time if you update applications regularly

You might, of course, want to update the schedule on the deployment until after the new content has been distributed, or to control the deployment time.

For OSD applications which can only have one deployment type, you’ll need to update the existing deployment type instead.

Granted, it won’t work in every scenario, especially if you need to uninstall the old version, although you could run an ‘uninstall’ deployment first, then schedule an ‘install’ deployment after.  But for simple applications at least, it seems to me a better way to manage application version updates.