If you’ve been dealing with PowerShell and ConfigMgr 2012 lately, you’ve probably found out that the most interesting properties of an application is stored in XML format in the SDMPackageXML property. On of the properties that is stored in SDMPackageXML under DeploymentTypes is the Contents. The value of Contents is the one you’d modify in order to change the path of an applications contents. The script below will do just this, but for all applications and only the latest version.
The script
[System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName "Microsoft.ConfigurationManagement.ApplicationManagement.dll")) | Out-Null [System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName "Microsoft.ConfigurationManagement.ApplicationManagement.Extender.dll")) | Out-Null [System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName "Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll")) | Out-Null $SiteServer = "<name_of_your_Primary_Site_server>" $SiteCode = "<your_site_code>" $CurrentContentPath = "\\\\<name_of_server_where_the_content_was_stored_previously\\<folder>\\<folder>" $UpdatedContentPath = "\\<name_of_the_server_where_the_content_is_stored_now\<folder>\<folder>" $Applications = Get-WmiObject -ComputerName $SiteServer -Namespace root\SMS\site_$SiteCode -class SMS_Application | Where-Object {$_.IsLatest -eq $True} $ApplicationCount = $Applications.Count Write-Output "" Write-Output "INFO: A total of $($ApplicationCount) applications will be modified`n" Write-Output "INFO: Value of current content path: $($CurrentContentPath)" Write-Output "INFO: Value of updated content path: $($UpdatedContentPath)`n" Write-Output "# What would you like to do?" Write-Output "# ---------------------------------------------------------------------" Write-Output "# 1. Verify first - Verify the applications new path before updating" Write-Output "# 2. Update now - Update the path on all applications" Write-Output "# ---------------------------------------------------------------------`n" $EnumAnswer = Read-Host "Please enter your selection [1,2] and press Enter" switch ($EnumAnswer) { 1 {$SetEnumAnswer = "Verify"} 2 {$SetEnumAnswer = "Update"} Default {$SetEnumAnswer = "Verify"} } if ($SetEnumAnswer -like "Verify") { Write-Output "" $Applications | ForEach-Object { $CheckApplication = [wmi]$_.__PATH $CheckApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($CheckApplication.SDMPackageXML,$True) foreach ($CheckDeploymentType in $CheckApplicationXML.DeploymentTypes) { $CheckInstaller = $CheckDeploymentType.Installer $CheckContents = $CheckInstaller.Contents[0] $CheckUpdatedPath = $CheckContents.Location -replace "$($CurrentContentPath)","$($UpdatedContentPath)" Write-Output "INFO: Current content path for $($_.LocalizedDisplayName):" Write-Host -ForegroundColor Green "$($CheckContents.Location)" Write-Output "UPDATE: Updated content path will be:" Write-Host -ForegroundColor Red "$($CheckUpdatedPath)`n" } } } if ($SetEnumAnswer -like "Update") { Write-Output "" $Applications | ForEach-Object { $Application = [wmi]$_.__PATH $ApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($Application.SDMPackageXML,$True) foreach ($DeploymentType in $ApplicationXML.DeploymentTypes) { $Installer = $DeploymentType.Installer $Contents = $Installer.Contents[0] $UpdatePath = $Contents.Location -replace "$($CurrentContentPath)","$($UpdatedContentPath)" if ($UpdatePath -ne $Contents.Location) { $UpdateContent = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentImporter]::CreateContentFromFolder($UpdatePath) $UpdateContent.FallbackToUnprotectedDP = $True $UpdateContent.OnFastNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::Download $UpdateContent.OnSlowNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::DoNothing $UpdateContent.PeerCache = $False $UpdateContent.PinOnClient = $False $Installer.Contents[0].ID = $UpdateContent.ID $Installer.Contents[0] = $UpdateContent } } $UpdatedXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::SerializeToString($ApplicationXML, $True) $Application.SDMPackageXML = $UpdatedXML $Application.Put() | Out-Null Write-Output "INFO: Updated content path for $($_.LocalizedDisplayName)" } }
Using the script
In order to use this script, save it as e.g. Update-ApplicationSource.ps1 and save it to a folder e.g. E:\Scripts as I’ve done in the pictures below. The script has 2 functions. The first function is to verify what will be changed and the second is to update the Contents property value. For it to work in your environment, you’d need to change four variables:
- $SiteServer = “<name_of_your_Primary_Site_server>“
- $SiteCode = “<your_site_code>“
- $CurrentContentPath = “\\\\<name_of_server_where_the_content_was_stored_previously\\<folder>\\<folder>“
- $UpdatedContentPath = “\\<name_of_the_server_where_the_content_is_stored_now\<folder>\<folder>“
When you’ve modified the script to work in your environment, do the following to run it:
1. Open an elevated PowerShell command prompt.
2. Browse to where you saved the script.
3. Run the following command:
.\Update-ApplicationSource.ps1
4. You’ll be asked to enter 1 or 2. Choose 1 if you’d like to verify what changes will be made. The script will only enumerate all the applications old content path and show you what the changed value would be. I’d recommend that you choose the verify option first and then relaunch the script again when you are confident that everything looks correct.
5. When you’ve verified your applications content path, relaunch the script by running the command above and choose option 2.
The script will now go through all the applications and update the content path. Depending on how many applications you have, the execution time will vary. As you can see I have 162 applications, and it took a couple of minutes for it to complete. As for a convenience, I’ve added so that the script will output what application it’s currently working on and what the current content path is so it doesn’t look like it has stalled.
I hope this helps!
Hi Nickolaj,
I just want to take some time to thank you and your team on this Blog. I postponed for months the move of 200 applications to the new server location. I finally get it done thanks to you and your awesome script. I’m learning a lot from SCCOnfigMgr 🙂
Hi Yuchi, I’m glad you managed to use the script successfully and that it worked out 🙂
Thanks – this helped me today.
I am trying to change only the server name of my content paths (new server, different name) because the file structure is like so…
Content Location: \\servername\drive letter\variable path\variable path\
I need to change ONLY the server name for ALL of my applications, but leave the rest of the UNC path alone. Thank yo
Hi Creed,
Have you seen my ConfigMgr Content Update Tool? It will do exactly what you’re looking for:
https://msendpointmgr.com/2015/08/26/configmgr-content-source-update-tool-1-0-0/
Regards,
Nickolaj
Excellent!! Thank you very much! This looks to be exactly what I am after. Great work.
Have you been able to migrate drivers like this as well?
Thank you for posting this script it’s exactly what I needed.
Added some error checking to the script, to ensure that the path exists on the target server before changing the content location.
https://github.com/1RedOne/SCCM-Cmdlets/blob/master/MoveAllContent.ps1
Hi Stephen,
Thank for sharing you addition to the script! I’ve built the same kind of code into the ConfigMgr Content Source Update tool that I released a few weeks ago.
Regards,
Nickolaj
Do you have a link to the Source Update tool? I am trying to change only the server name of my content paths (new server, different name) because the file structure is like so…
\\servername\drive letter\variable path\variable path\
I only need to change the server name for ALL of my applications, but leave the rest of the UNC path alone. Thank you!
Thanks, this has been helpful. However, it doesn’t appear to work for Mac apps. Whenever the script gets to a Mac app, it comes up with the following error:
Exception calling "SerializeToString" with "2" argument(s): "Invalid property: object
Application(ScopeId_FF13E674-8A8B-43B6-B91F-20F7F671189B:Application_bdddef32-5194-4e07-adb7-43827254711f:4) property
DeploymentTypes.DeploymentTypes[0].Installer.Installer.Contents: MacInstaller expects only one content file"
At line:67 char:9
+ $UpdatedXML = [Microsoft.ConfigurationManagement.ApplicationM ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : InvalidPropertyException
Exception calling "Put" with "0" argument(s): "Generic failure "
At line:69 char:9
+ $Application.Put() | Out-Null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
There are also a few Windows applications which are coming up with this error too:
Exception calling "Put" with "0" argument(s): "Generic failure "
At line:69 char:9
+ $Application.Put() | Out-Null
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException
+ FullyQualifiedErrorId : DotNetMethodException
Despite this, the location appears to be getting updated.
Spent about 35 minutes this morning trying to get this to work. No matter what I do…the new location path is always the old location path. I see this is the case in the example screenshots as well.
I’m noticing the same thing.
Did the same as Iain and it is now working. Thanks, Iain and Nickolaj!
Just worked out a small point – the “SiteServer” actually needs to be where the SMSProvider is installed. In most cases it will probably be the same server, but not always.
Hi Nic
I found your script useful but was wondering why you are updating every application even those that does not need that (same with display witch app has been updated)
more less after modification to our needs its great, initialy i build my script based on CM cmdlets but it was way to slow than querying wmi itself – Great hint!
I recommend changing the update process to sth like that
#start of scriptblock
if ($SetEnumAnswer -like “Update”) {
Write-Output “”
$Applications | ForEach-Object {
$Application = [wmi]$_.__PATH
$ApplicationXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($Application.SDMPackageXML,$True)
foreach ($DeploymentType in $ApplicationXML.DeploymentTypes) {
$Installer = $DeploymentType.Installer
$Contents = $Installer.Contents[0]
$UpdatePath = $Contents.Location -replace “$($CurrentContentPath)”,”$($UpdatedContentPath)”
if ($UpdatePath -ne $Contents.Location) {
$UpdateContent = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentImporter]::CreateContentFromFolder($UpdatePath)
$UpdateContent.FallbackToUnprotectedDP = $True
$UpdateContent.OnFastNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::Download
$UpdateContent.OnSlowNetwork = [Microsoft.ConfigurationManagement.ApplicationManagement.ContentHandlingMode]::DoNothing
$UpdateContent.PeerCache = $False
$UpdateContent.PinOnClient = $False
$Installer.Contents[0].ID = $UpdateContent.ID
$Installer.Contents[0] = $UpdateContent
#edited by Adrian – Updating only necessary apps
$UpdatedXML = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::SerializeToString($ApplicationXML, $True)
$Application.SDMPackageXML = $UpdatedXML
$Application.Put() | Out-Null
Write-Output “INFO: Updated content path for $($_.LocalizedDisplayName)”
}
}
}
}
#end of scriptblock
Hi Adrian,
Yeah, I figured that out as well some time ago and have since then updated the script a few times. I’ve not had time yet to update this blog post with the latest version, but thanks for sharing your thoughts and code snippets! 🙂
Indeed, this method is way faster than the cmdlets, they both use WMI but for some reason the cmdlet is a bit slow (probably more logic that it has to work through than in my script 😉 )
Regards,
Nickolaj
Hi Nickolaj,
Great script, saved me a bunch of time. Thanks.
I don’t know exactly why, but I had to change lines 38 and 55 to:
$CheckUpdatedPath = $CheckContents.Location -replace [regex]::Escape($CurrentContentPath),$UpdatedContentPath
And omit the double \\ in the input variable, as if I didn’t do this, the replace failed and it kept the same path.
Not really sure why…
Hi Iain,
Thanks! Interesting that you had to change that, but great that you found the solution. The script posted in this post worked great in that environment I was working in at the time.
Regards,
Nickolaj
Hi Nickolaj,
Thanks for a great script. As a suggestion, you could replace the first three lines with the following:
[System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName “Microsoft.ConfigurationManagement.ApplicationManagement.dll”)) | Out-Null
[System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName “Microsoft.ConfigurationManagement.ApplicationManagement.Extender.dll”)) | Out-Null
[System.Reflection.Assembly]::LoadFrom((Join-Path (Get-Item $env:SMS_ADMIN_UI_PATH).Parent.FullName “Microsoft.ConfigurationManagement.ApplicationManagement.MsiInstaller.dll”)) | Out-Null
That way it will find the CM binaries regardless of the OS version and installation path.
Hi Jerome,
That’s a great idea, I’ll update the script now! Thanks for sharing 🙂
Regards,
Nickolaj