MSEndpointMgr

Update the content path of all Applications in ConfigMgr 2012 with PowerShell

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.
43_1
5. When you’ve verified your applications content path, relaunch the script by running the command above and choose option 2.
43_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!

Nickolaj Andersen

Chief Technical Architect and Enterprise Mobility MVP since 2016. Nickolaj has been in the IT industry for the past 10 years specializing in Enterprise Mobility and Security, Windows devices and deployments including automation. Awarded as PowerShell Hero in 2015 by the community for his script and tools contributions. Creator of ConfigMgr Prerequisites Tool, ConfigMgr OSD FrontEnd, ConfigMgr WebService to name a few. Frequent speaker at conferences such as Microsoft Ignite, NIC Conference and IT/Dev Connections including nordic user groups.

21 comments

  • 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 🙂

  • 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

  • Have you been able to migrate drivers like this as well?
    Thank you for posting this script it’s exactly what I needed.

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

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

Sponsors