Peter van der Woude created an excellent PowerShell script and blog post to dynamically deploy applications to computers via a Task Sequence within ConfigMgr.

At a client recently, I was tasked with implementing this script. I decided that I would embellish the script and add in support for dynamic package deployment and also allow for cross-forest installation, trusted and non-trusted and workgroup client install.

This is achieved by running a section of the script as a service account which is given the Read-Only Analyst role in ConfigMgr.

The complete script can be downloaded from the Technet Galleries here.

Pre-Requisites

The following pre-requisites need to be configured to allow for Application Task Sequence deployment of dynamic apps and packages

  1. The script requires PowerShell v3 and above to run. If executing against Windows 2008 server OS then install .Net 4.0 and WMF 3.0
  2. A service account is required to run the Dynamic Apps/Packages script. This service account needs to be assigned the role of ‘Read-Only Analyst’ within the ConfigMgr 2012 console.
  3. Permissions are assigned for the service account to execute a remote PowerShell session on the ConfigMgr site server .

To do this run the following command on the site server in PowerShell (as administrator).

Set-PSSessionConfiguration microsoft.powershell –ShowSecurityDescriptorUI

Confirm by pressing ‘Y’ then add the relevant AD group and add in execute(invoke) permissions and apply.

DynApp-001

Press ‘Y’ again to confirm.

Creating Applications or Packages for Task Sequence Deployment

The standard method for creating an application or package with ConfigMgr 2012 should be followed but certain caveats exist for them to work as dynamic installs.

  • The maximum number of dynamic applications that can be deployed using the script is 99.
  • If creating a Package installation then an associated install Program needs to be created.
  • Task Sequences are a great way to install applications and their dependencies in a specific order. By changing the installation to a dynamic list there is no way to order the installs. Therefore any dependency chains should be set within the actual package or application. Note it is not possible to link dependencies between applications and package. Dependencies can only be set with the same model type.
  • Each install should be set to ‘allow installation’ from within a Task Sequence, otherwise that particular application or package will fail to install.

For an Application, right click on the application and choose Properties. In the General Information tab select ‘Allow this application to be installed from the Install Application task sequence action without being deployed’

DynApp-002

For a Package, right click the Program and choose Properties

DynApp-003

Select the Advanced tab and choose ‘Allow this program to be installed from the Install Package task sequence action without being deployed’

DynApp-004

  • It is recommended to assign a meaningful ‘Program Name’ to a Package as this will be written to the smsts.log when running the Task Sequence and helpful for troubleshooting if there are any issues.

For example: Install SCOM Agent

DynApp-005

  • It is recommended to use the package model within Task Sequence deployment rather than applications.

Configure ConfigMgr 2012 to cater for the Dynamic Apps/Packages script

Certain conditions must exist within ConfigMgr for the Dynamic Apps/Packages script to execute successfully. Without these conditions in place the script will not enumerate applications or packages.

  • A ‘Container Node’ should be created for all application or package install collections to be housed under. This container node, ‘Application Deployment’ in my example, is referenced in the dynamic apps/pack script as the starting point for gathering information on which collections the device being deployed is a member of.

DynApp-006

  • Individual ‘AppDeploy’ collections are created per application or package.

DynApp-007

  • Each collection has to have a relevant application or package deployed to it. The apps or package must be set as ‘Required’ deployments.

DynApp-008

  • Each deployment when set to ‘Required’ must be set to install ‘As soon as possible’ if a package or application.

DynApp-009

DynApp-010

Create the Package for the Dynamic Application Script

A package needs to be created within ConfigMgr to hold the dynamic application script. The package should be set with ‘No Program’ associated.

DynApp-011

In this instance the package ‘Dynamic Apps & Packages’ has been created to host the file.

DynApp-012

Create the Application Deployment Task Sequence and Deployment

The Task Sequence for dynamic application and package deployment consists of three steps:

  • A step to run the script and enumerate applications and packages targeted at the device being built
  • A step to install the applications targeted at the device
  • A step to install the packages targeted at the device

DynApp-013

Create the Dynamic Packages List

This step is a ‘Run PowerShell Script’ task.  The Dynamic Apps & Packages package highlighted in the previous section is the referenced package and the script ‘DynamicAppsPackages.ps1’ is added to the ‘Script Name’ box. The execution policy should always be set to ‘Bypass’ unless the script is signed.

DynApp-014

Install Packages

Packages that have been enumerated when the script executes will be stored as a list of variables. Starting with APPID001 and incrementing.

The ‘Install software packages according to dynamic variable list’ check box is selected and the ‘Base variable name’ APPid entered.

DynApp-015

A condition needs to be entered into the task sequence to skip the ‘Install Packages’ step if no packages are deployed to the device being built.

In the Options tab of the ‘Install Packages’ step a condition has been created. The criteria states:

‘If the SkipPackages task sequence variable does not exist then do not run the step’

The SkipPackages variable is created by the dynamic app/packages script only if no packages are deployed against the device. Therefore if this were the case the step would be ignored and the Task Sequence would continue.

DynApp-016

Install Applications

The ‘Install Applications’ step is identical to the ‘Install Package’ step but with minor differences.

Applications that have been enumerated when the script executes will be stored as a list of variables. Starting with APPID01.  Note a two digit, rather than three, number.

The ‘Install applications according to dynamic variable list’ check box is selected and the ‘Base variable name’ APPid entered.

DynApp-017

Again a condition needs to be entered into the task sequence to skip the ‘Install Applications’ step if no applications are deployed to the device being built.

In the Options tab of the ‘Install Applications’ step a condition has been created. The criteria states:

‘If the SkipApplications task sequence variable does not exist then do not run the step’

As with the ‘SkipPackages’ variable, ‘SkipApplications’ is created by the dynamic app/packages script, only if no packages are deployed against the device, and the step will be ignored.

DynApp-018

Create the Task Sequence deployment

The ‘Automation Dynamic Applications’ Task Sequence is deployed as a ‘Required’ deployment to the ‘Automation Dynamic Applications’ collection.

DynApp-019

The Dynamic Applications & Packages script breakdown

The complete script can be downloaded from the Technet Galleries here.

This section takes the script apart and explains what is happening in each section of the script.

The script can be broken down into three areas:

  1. Variables and credentials are set locally and a commands are invoked to run a remote session as a service account user.
  2. Enumeration of applications and packages take place on a remote session on the site server
  3. Results are brought back to the local session and then stored as Task Sequence variables.

The example code is run against a test device which has three packages and one application deployed to it.

Variables

The following variables are defined in the script

# $ResourceName = local hostname of the device running the script
# $password = password of service account running the app/pack enumeration for the device
# $cred = Creates a PSCredential object of username/password that will run the remote session on behalf of the device
# $CountPack = counter for package enumeration
# $CountApp = counter for app enumeration
# $SiteCode = ConfigMgr site for the environment
# $SiteServer = ConfigMgr site server hostname for the environment
# $Container = Container Node in ConfigMgr that stores all the application and package deployment collections
# $PackageEnumerate = Array to store all packages in the remote session
# $ApplicationEnumerate = Array to store all applications in the remote session
# $localPackages = Variable to store all packages when back in local session before passing to Task Sequence
# $localApplications = Variable to store all packages when back in local session before passing to Task Sequence

Section 1 – Some local variables defined

$tsenv = New-Object -COMObject Microsoft.SMS.TSEnvironment
$password = $tsenv.Value("svcaccpassword")
$ResourceName = $env:computername
$cred = New-Object System.Management.Automation.PSCredential -ArgumentList @("<domain\username>",(ConvertTo-SecureString -String $password -AsPlainText -Force))
$CountPack = 1
$CountApp = 1

A password, for the service account that will run the remote session, is set as a collection variable on the collection targeted for deployment by the Task Sequence, read from the Task Sequence environment ($tsenv.Value(“svcaccpassword”) ) and stored as the variable $password.

DynApp-020

This is assigned into a PSCredential object along with the service account username . Note that the $cred variable in the script needs to be amended to add the domain\username for the service account running the remote session.

$cred = New-Object System.Management.Automation.PSCredential -ArgumentList @("<domain\username>",(ConvertTo-SecureString -String $password -AsPlainText -Force))

Counters for package and application enumeration are set to 1. They will be used later in the script to create the APPID001/01, APPDID002/02 variables.

Section 2 – Scriptblock: The Remote Session

The following section of PowerShell code is the ‘meat’ of the piece. It runs in a remote session on the site server and is called from section 3 of the script. It’s the section that will enumerate all the applications and packages deployed to the device.

The code’s results are stored as a variable called $ScriptBlockContent and all the code is contained within parenthesis

$ScriptBlockContent = { CODE HERE }

Onto the code:

param ($ResourceName)
$SiteCode = "<SITE CODE>"
$SiteServer = "<SITE SERVER>"
$Container = "Application Deployment"
Write-Host "$ResourceName"
$PackageEnumerate = @()
$ApplicationEnumerate = @()

Note – change the $SiteCode and $SiteServer variables to reflect your ConfigMgr site details.

Since the code runs in the remote session, the local hostname of the device defined in Section 1 needs to be passed over to the remote session. The invoke-command in Section 3 achieves this, but the param ($ResourceName) defined in Section 2 accepts the local to remote variable in the remote session.

Other essential variables are defined before running the queries to enumerate applications and packages (e.g. site code – see Variables section for definitions). Note a write-host of $ResourceName. This will write the hostname of the device into ConfigMgr’s smsts.log file (the log file used to check Task Sequence’s). This is a visual check in the log file so we can ensure the code is running against the correct device should any error checking be required.

The code moves into the query phase to collate the app/package information we need to extract.

$ContainerNodeId = (Get-WmiObject -ComputerName $SiteServer -Class SMS_ObjectContainerNode -Namespace root/SMS/site_$SiteCode -Filter "Name='$Container' and ObjectTypeName='SMS_Collection_Device'").ContainerNodeId

After running the code on the site server it is determined that the ContainerNodeID for the “Application Deployment” container node is ‘16777253’, in this example.  Note that the ID will never change for this node.

DynApp-021

$CollectionIds = (Get-WmiObject -ComputerName $SiteServer -Namespace root/SMS/site_$SiteCode -Query "SELECT fcm.* FROM SMS_FullCollectionMembership fcm, SMS_ObjectContainerItem oci WHERE oci.ContainerNodeID='$ContainerNodeId' AND fcm.Name='$ResourceName' AND fcm.CollectionID=oci.InstanceKey").CollectionId

The ContainerNodeID can then be used to link two WMI classes and return the list of collections within the Container Node which the device is a member of. The query above achieves this.

WMI Explorer can effectively show how this query works.

Running a query of SELECT * FROM SMS_FullCollectionMembership where Name = ‘<myhostname>’ retrieves all collection membership for the device.  (Note ResourceID = 16777679)

DynApp-022

 

For the purposes of the application/package enumeration, only the collection membership within the Container Node ‘Application Deployment’ is required, since this the location where all apps & packages are deployed to.

Running a query of SELECT * FROM SMS_ObjectContainerItem where ContainerNodeID = ‘16777253’ returns all the collections that contained within the container node.

DynApp-023

Note that the InstanceKey in each result can be matched against the CollectionID from the FullCollectionMembership query and therefore the results can be filtered down to only the collections in the Container Node that the device is a member of.

The final query gives the required result

SELECT fcm.* FROM SMS_FullCollectionMembership fcm, SMS_ObjectContainerItem oci WHERE oci.ContainerNodeID='$ContainerNodeId' AND fcm.Name='$ResourceName' AND fcm.CollectionID=oci.InstanceKey

Collection membership of four collections within the container node ‘Application Deployment’

DynApp-024

DynApp-025

if ($CollectionIds -ne $null) {
foreach ($CollectionId in $CollectionIds) {
$ApplicationNames = (Get-WmiObject -ComputerName $SiteServer -Class SMS_ApplicationAssignment -Namespace root/SMS/site_$SiteCode -Filter "TargetCollectionID='$CollectionId' and OfferTypeID='0'").ApplicationName
$AdvertisementDetails = Get-WmiObject -ComputerName $SiteServer -Class SMS_AdvertisementInfo -Namespace root/SMS/site_$SiteCode -Filter "CollectionID='$CollectionID' and PackageType='0'"

if ($AdvertisementDetails -ne $null) {foreach ($AdvertisementDetail in $AdvertisementDetails) {
$PackageEnumerate += ($AdvertisementDetail.PackageID+':'+$AdvertisementDetail.ProgramName)
}
}

if ($ApplicationNames -ne $null) {foreach ($ApplicationName in $ApplicationNames) {
$ApplicationEnumerate += $ApplicationName
}
}

The next step of the code loops for each result collected, e.g. for each Collection ID.

Initially the code checks to see if no Collection ID were returned. If Collection ID’s were returned in the previous query then execute the loop.

 

$ApplicationNames = (Get-WmiObject -ComputerName $SiteServer -Class SMS_ApplicationAssignment -Namespace root/SMS/site_$SiteCode -Filter "TargetCollectionID='$CollectionId' and OfferTypeID='0'").ApplicationName

The code checks in the WMI class ‘SMS_ApplicationAssignment’ for each Collection ID, ensures that each targeted collection is a ‘Required’ deployment and stores the application name deployed to the collection in a variable called $ApplicationNames.

DynApp-026

For my test device one application has been enumerated – ‘FileZilla_FTP_Client  – Unattended’

A similar loop is applied to packages.

$AdvertisementDetails = Get-WmiObject -ComputerName $SiteServer -Class SMS_AdvertisementInfo -Namespace root/SMS/site_$SiteCode -Filter "CollectionID='$CollectionID' and PackageType='0'"

The code checks the WMI class ‘SMS_AdvertisementInfo’ for each CollectionID. Ensures that each targeted deployment is of type ‘Package’ and stores all the advertisements details into the variable $AdvertisementDetails.

DynApp-027

For my test device three deployments are enumerated: SITECODE20081, SITECODE20080 & SITECODE2007F.

Finally the enumerated results of the application and package search need to be stored into separate arrays, $PackageEnumerate, $ApplicationEnumerate, which can be retrieved from the remote session.

if ($AdvertisementDetails -ne $null) {foreach ($AdvertisementDetail in $AdvertisementDetails) {
$PackageEnumerate += ($AdvertisementDetail.PackageID+':'+$AdvertisementDetail.ProgramName)
}
}

if ($ApplicationNames -ne $null) {foreach ($ApplicationName in $ApplicationNames) {
$ApplicationEnumerate += $ApplicationName
}
}

The code takes the results from the $AdvertisementDetails and stores the deployments PackageID and ProgramName for each Package separated by a colon. Note this is the format the Task Sequence expects for the dynamic package list.

For example: SITECODE00043:Install SCOM Agent

Note – Look at WMI class that the advertisement details were retrieved from to see what properties are available to use.

DynApp-028

The results from $ApplicationNames are stored into an array $ApplicationsEnumerate. Only the ApplicationName property is needed for the Task Sequence dynamic applications list.

DynApp-029

Section 3 – Invoking the Scriptblock and writing back to the Task Sequence

$Session = New-PSSession -ComputerName <site server> -Credential $cred
Invoke-Command -Session $Session -ScriptBlock $ScriptBlockContent -ArgumentList $ResourceName

A new PowerShell session is created on the site server, using the credentials supplied in ‘Section 1 – Some local variables defined ’. Note: set the -ComputerName variable to your site server host name

The invoke-Command runs the scriptblock in the session, and passes though the local variable of $ResouceName (or local hostname) to the remote session.

$localPackages = Invoke-Command -Session $Session -ScriptBlock {$PackageEnumerate}
$localApplications = Invoke-Command -Session $Session -ScriptBlock {$ApplicationEnumerate}
$TSEnv = New-Object -COMObject Microsoft.SMS.TSEnvironment

The code brings the arrays $PackageEnumerate and $ApplicationEnumerate back to the local session and they are stored as $localPackages and $localApplications respectively.

A Task Sequence COM object is created as the variable $TSEnv to allow the arrays to be written back to the TS as Task Sequence variables.

if ($localPackages -ne $null) {foreach ($Package in $localPackages) {
$Id = "{0:D3}" -f $CountPack
$AppId = "APPID$Id"
$TSEnv.Value($AppId) = $Package
Write-Host $AppId $Package
$CountPack = $CountPack + 1
}
}

The code now checks to ensure that $localPackages is not an empty variable and if not process each ‘$Package’ detail in $localPackages. It creates a three digit ID ({0:D3} and with the value of $CountPack, 1, being the first value of the ID. So 001. The variable APPID becomes ‘APPID + 001’ or APPID001 and this value is written to the Task Sequence with the value of $Package.

So APPID001 could be SITECODE00043:Install SCOM Agent.

A write-host exists so that the data is written to the ConfigMgr smsts.log file as a visual aid to error checking.

$CountPack is incremented by 1 and the loop continues until all variables in $localPackages are written back to the Task Sequence. Note the $TSEnv variable is remmed out in this example as it will only generate errors when run outside of the Task Sequence environment.

DynApp-030

if ($localApplications -ne $null) {foreach ($Application in $localApplications) {
$Id = "{0:D2}" -f $CountApp
$AppId = "APPID$Id"
$TSEnv.Value($AppId) = $Application
Write-Host $AppId $Application
$CountApp = $CountApp + 1
}
}

The application code works in exactly the same way, except that it only generates a two digit APPID and writes back in the format APPID01 ApplicationName

For example, APPID01 FileZilla_FTP_Client – Unattended

DynApp-031

Section 4 – SkipPackages & SkipApplications

As mentioned previously the Task Sequence requires conditions to skip the steps for application or package install if no deployments are targeted to the device for either type of installation. If these conditions are not applied the Task Sequence will error and fail.

 

if ($CountPack -eq "1") {
$TSEnv.Value("SkipPackages") = "True"
write-host "Skip Packages"
}

if ($CountApp -eq "1") {
$TSEnv.Value("SkipApplications") = "True"
Write-Host "Skip Applications"
}

The final lines of code check to see of the $CountPack and $CountApp variables are still equal to 1. If they are it means that no deployments are assigned.

So for each type the Task Sequence variables SkipPackages or SkipApplications are written back to the Task Sequence environment.

Remember that the TS has been set to not run the relevant steps if the variables exist.

DynApp-016

DynApp-018

The complete script can be downloaded from the Technet Galleries here.

 

Advertisements