Content download problems with large packages in ConfigMgr 2012

The customer was trying to install an AutoCAD type application via Configuration Manager 2012 (SP1 CU2) and the installation kept hanging in Software Center with the message “downloading data”.

The background is that this application had previously worked ok, but since then the source files have been altered and updated on the DPs. The source data is itself around a whopping 9Gb.

So let’s go through the logs.

AppDiscovery, AppEnforce and AppIntentEval all look normal. Application is not detected and will be installed.

Execmgr makes a call to CAS requesting content. So far so good.

CAS requests content locations and receives 3 valid DPs.

CAS submits a CTM (Content Transfer Manager) job, and receives confirmation of the locations and submits a DTS (Data Transfer Service) job.

Now the DTS starts the download and immediately hits a problem.

GetDirectoryList_HTTP(‘http://dp:80/SMS_DP_SMSPKG$/Content_45106a31-c15b-4d29-ba68-97e1b97a5e9e.1’) failed with code 0x80004005.

Error retrieving manifest (0x80004005).  Will attempt retry 1 in 30 seconds.

Sure enough it retries only to get:

Non-recoverable error retrieving manifest (0x80004005).

And this happens in turn for each available DP. Unfortunately Software Center will just sit there doing nothing. This last point may be because the installation is running in a task sequence but let’s not dwell on that for now.

So obviously a problem with the content on the DPs. Agreed?

Redistribute application to DPs – same error

Remove application from all Dps and redistribute – same error

Zip the source from 9gb down to 4Gb, remove from DPs and redistribute – same error, but wait, when i redistributed to the DPs it still took a long time, not half the time as i expected.

I had to get the Content ID from the DTS log and then look at the ContentLibrary on the DP. Found the content but it didn’t match the source. In fact it didn’t match any updated source, seemed to be the original content from before the first update. Very strange.

So to look at how distmgr and ContentLibrary work in more detail:

Source is copied to the “primary site server” and stored in a ContentLibrary there, even though there is no DP on this server. This is a small throwback to SCCM 2007 and is unavoidable. Anyway, from there the content is copied to the DPs and stored in ContentLibrary. Removing an application from the DP goes quite quickly in the console but looking at the  distmgr log and smsdpprov log on the DP you can see that the data itself is not fully removed for some time after, depending on the size of the content. If you redistribute the content before it is fully removed from ContentLibrary then distmgr will skip copying the files that already exist. Also, distmgr will copy the content from the ContentLibrary on the primary server, if it exists there, rather than directly from the source. So in actual fact, with a 9Gb application the source was never correctly updated on the DPs – it’s very difficult to say exactly which files didn’t match the hash values in the manifest but obviously we need to completely this application’s content from the DPs, from the primary server ContentLibrary and wait until everything is completely gone before redistributing.

Easier said than done. To remove from the DPs is not so difficult, just as with any normal application. But make sure that distmgr and smsdpprov confirm it is gone, AND check manually in ContentLibrary in DataLib under the ContentID to make sure it is physically gone. For 9Gb this can take about 30 minutes.

Then have a look at the ContentLib on the primary server. It hasn’t detected that it needs to update from the source so you need to trigger that. Here is what i did although maybe there is a better way, i had run out of patience by this point so i didn’t wait to see if it would also remove itself.

I removed all previous revisions of the application from revision history. Then changed the source path on the deployment type to point to an empty folder. Now monitoring distmgr i can see he has spotted this and creates a new content instance in the library with a new ContentID. The old ContentID however remains and is flagged as orphaned. Check ContentLibrary for the physical presence and this query against the DB:

select * from OrphanedContents

After a maximum of 60 minutes, the content cleanup cycle will run on the server and remove these orphaned contents. That’s about how long it takes to find out that there is very little documentation on the content cleanup task on the internet, and very little help to be found searching for “delete orphaned content sccm 2012” in Bing or Google or whatever… It helps pass the time though.

So now we have a large application with an empty source path, and not distributed to DPs. And checking that we can see that there are absolutely no more traces on DPs, in ContentLibraries, or in the DB.

Now set the source back to the correct source path on the deployment type. And wait until this is completely updated on the primary site in ContentLibrary. I didn’t wait this time for the cleanup task but i checked back later and references to the empty source where gone.

Check the ContentLibrary on the primary server, you need the new ContentID from distmgr.log, to see if the new content is physically there. Once it is distribute to DPs. Again check distmgr, also PkgXferMgr.log, on the primary server and smsdpprov.log on a DP until they are finished processing the content. Check the content is physically in the ContentLibrary in DataLib under the ContentID.

Now try installing the application again on the client. This time it installs no problem.

A couple of things to point out here:

This deployment is an application delivered via task sequence but it applies to pure AppModel deployments as well.

We have a primary server which serves as SMSProvider and the DPs are all only on separate boxes.

The problem is here when you have a very large amount of data in the source and you update through the console quicker than it actually takes for the processes to finish. On smaller content this may be ok but the larger the packet the more chance that something will get skipped in the file copy process.

Advertisement

Create Collections in ConfigMgr 2012 with Powershell

Not got time to write the usual three-page idiosyncratic blurb, here’s a script which will create four different collections for a pre-existing Application and move the collections to specific Folders (As usual the word wrap has probably screwed it up a little so i have attached the script as ps1 as well here):

Function CreateCollection($collName,$limitingColl,$container,$ud_Scope){

$collInstance = ([wmiclass]”\\server\root\sms\site_001:sms_collection”).CreateInstance()

$collInstance.Name = $collName

$collInstance.LimitToCollectionID = $limitingColl

if($ud_Scope -eq “device”){

$collInstance.CollectionType = 2

}elseif($ud_Scope -eq “user”){

$collInstance.CollectionType = 1

}

if(gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_collection | ?{$_.Name -eq $collName}){

“Collection $collName already exists”

}else{

$collInstance.Put()

$collInstance.Get()

$containerItemInstance = ([wmiclass]”\\server\root\sms\site_001:sms_objectcontaineritem”).CreateInstance()

$containerItemInstance.ContainerNodeID = $container

$containerItemInstance.InstanceKey = $collInstance.CollectionID

if($ud_Scope -eq “device”){

$containerItemInstance.ObjectType = 5000

$containerItemInstance.ObjectTypeName = ‘SMS_Collection_Device’

}elseif($ud_Scope -eq “user”){

$containerItemInstance.ObjectType = 5001

$containerItemInstance.ObjectTypeName = ‘SMS_Collection_User’

}

$containerItemInstance.SourceSite = ‘001’

$containerItemInstance.Put()

}

}

$scope = Read-Host -Prompt “Enter Scope ID (DEV,INT,PRD)”

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’} | %{

$parentContainerID = $_.parentContainerNodeID

if(gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.ContainerNodeID -eq $parentContainerID -and $_.Name -eq $scope}){

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_Device”} | %{

$parentContainerID = $_.ContainerNodeID

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Requests’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_Device”} | %{

$devContainerID = $_.ContainerNodeID

}

}

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Distribution’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_User”} | %{

$parentContainerID = $_.ContainerNodeID

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_objectcontainernode | ?{$_.Name -eq ‘Software Requests’ -and $_.parentContainerNodeID -eq $parentContainerID -and $_.ObjectTypeName -eq “SMS_Collection_User”} | %{

$usrContainerID = $_.ContainerNodeID

}

}

}

}

$devLimitingCollID = (gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_collection | ?{$_.Name -eq “Root-$scope”} | Select CollectionID).CollectionID

$usrLimitingCollID = “SMS00002”

 

gwmi -ComputerName server -Namespace root\sms\site_001 -Class sms_applicationlatest | ?{$_.SecuredScopeNames -contains $scope -and $_.LocalizedDisplayName -eq ‘OracleClient’} | select LocalizedDisplayName | %{

$appName = $_.LocalizedDisplayName

$collectionName = “$appName – Install Device”

CreateCollection $collectionName $devLimitingCollID $devContainerID “device”

$collectionName = “$appName – Uninstall Device”

CreateCollection $collectionName $devLimitingCollID $devContainerID “device”

$collectionName = “$appName – Install User”

CreateCollection $collectionName $usrLimitingCollID $usrContainerID “user”

$collectionName = “$appName – Uninstall User”

CreateCollection $collectionName $usrLimitingCollID $usrContainerID “user”

}

Create a Tree View of Applications and Dependencies

Here’s a quick code to get a tree view (sort of) of applications in your ConfigMgr 2012 environment that shows dependencies, including dependencies of dependencies. Works as is, for me at least. If you spot any bugs or improvements feel free to let me know. As usual watch out for word-wrapping and typos.

Function Get-Dependency($appCIID,$appNameFunc){

gwmi -ComputerName $server -Namespace “root\sms\site_$code -Class SMS_AppDependenceRelation -Filter “FromApplicationCIID=’$appCIID | %{

if($_.ToApplicationCIID -ne $null){

$ToApplicationCIID = $_.ToApplicationCIID

$dependencyName = Resolve-ApplicationName $ToApplicationCIID    

$dependencyNameSub = ” –>$dependencyName

Write-Host $dependencyNameSub     

Get-SubDependency $ToApplicationCIID $dependencyNameSub   

}

 }

return

}

Function Get-SubDependency($appCIID, $appNameFunc){

$appNamePrefix = ” “ + ($appNameFunc -split “>”)[0] + “>”

gwmi -ComputerName $server -Namespace “root\sms\site_$code -Class SMS_AppDependenceRelation -Filter “FromApplicationCIID=’$appCIID | %{

if($_.ToApplicationCIID -ne $null){

$ToApplicationCIID = $_.ToApplicationCIID

$dependencyName = Resolve-ApplicationName $ToApplicationCIID   

$dependencyName =$appNamePrefix$dependencyName

Write-Host$dependencyName

Get-SubDependency $ToApplicationCIID $dependencyName   

}

}

return

}

Function Resolve-ApplicationName($appCIID){

gwmi -ComputerName $server -Namespace “root\sms\site_$code -Class SMS_ApplicationLatest -Filter “CI_ID = ‘$appCIID‘” | select LocalizedDisplayName | %{

return $_.LocalizedDisplayName

}

}

cls

$server = “serverName”

$code = “001”

gwmi -ComputerName $server -Namespace “root\sms\site_$code -Class SMS_ApplicationLatest | %{

$appName = $_.LocalizedDisplayName

$CIID = $_.CI_ID

Write-Host$appName

Get-Dependency $CIID $appName

}

Enable TPM in a Task Sequence (DELL)

It took a while but I found a way to enable the TPM in the BIOS, then activate.

At first glance it should have been easy. Using the CCTK from Dell, set the TPM to enable from a command line and then a reboot and finally activate and a further reboot. But it would always only enable and never activate.

This is because the TPM would create an ownership on the chip itself and while the chip is ‘owned’ it cannot be activated. At least not by the CCTK.

In the end, after a lot of trial and error, I found a way to guarantee the chip is enabled and activated and ready for BitLocker, so here are the steps:

1. Set BIOS password with CCTK: Create a package from the Dell CCTK in ConfigMgr. Use this Package witha Command Line Step in the Task Sequence to set a setup password “cctk –setuppwd=password”

2. Enable TPM with CCTK: Using the Package again, run CCTK to enable TPM “cctk –tpm=on –valsetuppwd=password”

3. Restart Computer – if you perform these actions in the BIOS itself then you don’t need to restart. But here in a Task Sequence the WMI Classes need to be reloaded in order that we can see the Win32_TPM class.

4. Clean the chip ownership: I use a powershell script here but you can use vbscript. If you use powershell then you need to first set the Execution Policy with a command line step: “powershell.exe -noprofile -command “&{set-executionpolicy unrestricted -force}” then call the following script:

$oTPM = gwmi -Class Win32_TPM -Namespace root\CIMV2\Security\MicrosoftTpm

$oTPM.SetPhysicalPresenceRequest(10)

If(!(($oTPM.IsEndorsementKeyPairPresent()).IsEndorsementKeyPairPresent)){

$oTPM.CreateEndorsementKeyPair()

}

If(($oTPM.IsEndorsementKeyPairPresent()).IsEndorsementKeyPairPresent){

$OwnerAuth=$oTPM.ConvertToOwnerAuth(“customrandompassword”)

$oTPM.Clear($OwnerAuth.OwnerAuth)

$oTPM.TakeOwnership($OwnerAuth.OwnerAuth)

}

5. Restart again, this time you will get a prompt at the BIOS to press F10 to accept the chip changes

6. Activate the TPM with CCTK: “cctk –tpmactivation=activate –valsetuppwd=password”

7. Restart again.

That’s it.

ConfigMgr 2012 – Change Application Source Path

Big thanks to Angel Redondo for helping out with this.

Changing the source path for packages in SCCM 2007 was pretty easy over the SMSProvider.
It’s a bit different in ConfigMgr 2012.

Looking through the WMI Classes you’ll find SMS_Application, SMS_DeploymentType, and eventually SMS_Content. This last one has the source path for the application content but you’ll soon find out you can’t manipulate this because it is a system class and only allowed to be used by the provider itself.

What you need to do is leverage the dlls provided with the AdminConsole. Then you need to understand that information in ConfigMgr is handled completely differently from 2007. That being data is stored in XML format in WMI and SQL to allow for SQL Replication to look after much of the hierarchical and global replication. It is really good and much more efficient but might take a couple of attempts to get used to. Also the content source is defined on the Deployment Type object.

To alter Applications, in particular something like the source path, you need to connect to the Application class, the instance and then deserialize the XML data. Then you can edit this information, serialize it and inject it back into the WMI object.

I am assuming Powershell and that the Admin Console is installed on the machine running the script.
First of all get your ConfigMgr assemblies into your script:
#import assemblies
[System.Reflection.Assembly]::LoadFrom(“C:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.dll”)
[System.Reflection.Assembly]::LoadFrom(“C:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.Extender.dll”)
[System.Reflection.Assembly]::LoadFrom(“C:\Program Files\Microsoft Configuration Manager\AdminConsole\bin\Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll”)

#Specify your site server and sitecode:
$server = “configmgr01”
$code = “001”

#Get the applications (i’m only doing the latest version here):
#gwmi -ComputerName $server -Namespace root\sms\site_$code -class sms_application | ?{$_.IsLatest -eq $true}

#Either save this an object or loop on the fly, i’m looping on the fly:

gwmi -ComputerName $server -Namespace root\sms\site_$code -class sms_application | ?{$_.IsLatest -eq $true} | %{
#get the instance of the application
$app = [wmi]$_.__PATH
#deserialize the XML data
$appXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($app.SDMPackageXML,$true)
#loop through the deployment types
foreach($dt in $appXML.DeploymentTypes){
#find the installer element of the XML
$installer = $dt.Installer
#the content for each installer is stored as an single element array
$content = $installer.Contents[0]
“Current Path: ” + $content.Location
#use a regular expression to modify the existing path to your new location
$newPath = $content.Location -replace ‘\\\\domain.loc\\unc\\share’,’\\namespace.loc\dfs’
“New Path : ” + $newPath
if($newPath -ne $content.Location){
“Setting new path”
#this creates a new instance of content which will overwrite the older, this works cleaner than modifying the existing content path
#modifying existing tends to corrupt the data
$newContent = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentImporter]::CreateContentFromFolder($newPath)
$newContent.FallbackToUnprotectedDP = $true
$newContent.OnFastNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::Download
$newContent.OnSlowNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::Download
$newContent.PeerCache = $false
$newContent.PinOnClient = $false
#set the content ID to the new content information you have created
$installer.Contents[0].ID = $newContent.ID
#inject the new data into the deserialized XML data
$installer.Contents[0] = $newContent
}
}
#reserialize the XML
$updatedXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::SerializeToString($appXML, $true)
#add the serialized XML data to the application object
$app.SDMPackageXML = $updatedXML
#put the changes to the instance
$app.Put()
}

Using Functions with Optional and Required Parameters

Just a quick example of how to pass parameters to a powershell function. This works also as script parameters…

#——————–
function test{
Param($inOptional1=”Hi”,$inRequired1=$(throw “inRequired1 is a required input”),$inOptional2=$null)
Write-Host $inOptional1
Write-Host $inRequired1
Write-Host $inOptional2
}
“——————-”
test “Hello” “World” “Lovely day”
“——————-”
test -inOptional1 “Hello” -inRequired1 “World”
“——————-”
test -inOptional1 “Hello” -inRequired1 “World” -inOptional2 “Lovely day”
“——————-”
test -inRequired1 “World”
“——————-”
test -inOptional1 “Hello”
“——————-“

Adding Roles and Features in SCCM Task Sequence

Should be simple. And I have done it before and it was, dead simple. This post kinda overlaps with running powershell scripts, batch files, and command lines from Task Sequence, and the example here is Roles and Features.

Sticking with deploying Server 2008 R2 for simplicity, I want to add Roles and Features after the OS is installed: .NET Framework, and RDS, for XenApp Servers.

Previously, you could use ServerManagerCmd.exe for this, so when deploying 2008 or earlier then you need to modify accordingly. ServerManagerCmd is deprecated in R2 so you need to use Powershell, and the servermanager module.

First thing is to set the exec policy: i use a command-line step in the Task Sequence:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command “&{Set-ExecutionPolicy Unrestricted -Force}”

But you can also use a command line with Reg:

reg add HKLM\Software\Microsoft\Powershell\1\ShellIds\Microsoft.Powershell /v ExecutionPolicy /t REG_SZ /d Unrestricted /f

Of course you need to run from command line for the posh variant because you can’t use a powershell script to set the execution policy when the policy already restricts scripts from running – a chicken and egg analogy. Incidentally, the egg came first. A reptile egg presumably, but it hatched into a genetic mutation which in turn laid a mutant egg and this process eventually led to to the chicken being hatched from a non-chicken egg. So perhaps the chicken did come first then if you mean which came first, chicken or chicken’s egg? Otherwise it’s egg. Maybe better to think how you might get your car keys out of your locked car. That’s a simpler conundrum.

Anyway, onward. Another command-line:

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -noprofile -command “&{Import-Module -Name C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ServerManager\servermanager.psd1;Add-WindowsFeature AS-NET-Framework -includeAllSubFeature}”

The option with this last command-line is that you can put it in a script with a whole host of other post install tasks or changes, package it and call from the task sequence. Obviously your script only needs the part in the “{}”. Interestingly I was getting errors last time with paths, hence the full paths to the exe and psd1, but it does work without these so long as working dir etc is correct.

So that’s a pretty easy way to install a role or feature. But you could also use the MDT integration with SCCM to create a MDT Task Sequence.

For MDT you need to configure the MDT integration, and create an MDT Tools Package: when you create a new MDT TS you can create the Tools package in the wizard.

Be careful though, for some reason if you use a standard SCCM TS and add MDT steps they don’t always work. It’s easier to create a whole MDT TS and modify it accordingly, particularly for Roles and Features. For installing Language Packs, you can use a standard SCCM TS and add the Use MDT Toolkit step, then Language Pack Offline step and it works fine. Not so with Roles/Features. Funny.

Err on the side of caution I say, create the MDT TS and remove the extraneous steps and groups. It’ll save you time. Time enough to figure out how to get your keys…

Configure ConfigMgr Pre-Reqs with Powershell – Update 2012

No WebDav…. The following script will set up all the neccessary roles and features for a SCCM 2012 Site Server:

# add Windows Features Required for SCCM Site Server
# add Server Manager CMDLETS
Import-Module servermanager

# add Windows Features Required for SCCM Site Server
Add-WindowsFeature WAS-Process-Model
Add-WindowsFeature WAS-Config-APIs
Add-WindowsFeature WAS-Net-Environment
Add-WindowsFeature Web-Server
Add-WindowsFeature Web-ISAPI-Ext
Add-WindowsFeature Web-ISAPI-Filter
Add-WindowsFeature Web-Net-Ext
Add-WindowsFeature Web-ASP-Net
Add-WindowsFeature Web-ASP
Add-WindowsFeature Web-Windows-Auth
Add-WindowsFeature Web-Basic-Auth
Add-WindowsFeature Web-URL-Auth
Add-WindowsFeature Web-IP-Security
Add-WindowsFeature Web-Scripting-Tools
Add-WindowsFeature Web-Mgmt-Service
Add-WindowsFeature Web-Lgcy-Scripting
Add-WindowsFeature Web-Lgcy-Mgmt-Console
Add-WindowsFeature Web-Stat-Compression
Add-WindowsFeature Web-Metabase
Add-WindowsFeature Web-WMI
Add-WindowsFeature Web-HTTP-Redirect
Add-WindowsFeature Web-Log-Libraries
Add-WindowsFeature Web-HTTP-Tracing
Add-WindowsFeature BITS-IIS-Ext
Add-WindowsFeature Net-Framework-Core
Add-WindowsFeature RDC

SCCM Add Computer Associations with Powershell – Download Executable

Ok, here is an exe version of this tool. The code inside is also a bit tidier.
Download the zip file from here:
https://sites.google.com/site/andrewcraigsccm/ACATSV409.zip

Extract and copy the files to a location of choice but make a note of where.
Run the exe from a powershell or command window, or create a shortcut. You must use the following command line syntax:

powershell.exe [pathToExecutable] -Arguments -ConfigFile [pathToXML]
For Example:
powershell.exe F:\scripts\sccm\ACATSV409.exe -Arguments -ConfigFile “F:\scripts\SCCM”

When run from a powershell window of course you don’t need powershell.exe at the front.
At first run you will get a message to insert site provider (fqdn of sccm site server) and then site code (xxx).

That’s it, have fun. The IP Address and Firmware functions on the front page just set Machine Variables – in order to use these you need to create Task Sequences with Conditions.