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!

(1149)

Nickolaj Andersen

Principal Consultant 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. Currently working for TrueSec as a Principal Consultant. 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 and user groups.

comments
  • Jerome Brown
    Posted at 04:01 August 7, 2013
    Jerome Brown
    Reply
    Author

    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.

    • Nickolaj
      Posted at 09:20 August 7, 2013
      Nickolaj
      Reply
      Author

      Hi Jerome,

      That’s a great idea, I’ll update the script now! Thanks for sharing ๐Ÿ™‚

      Regards,
      Nickolaj

  • Iain
    Posted at 18:00 March 18, 2014
    Iain
    Reply
    Author

    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…

    • Nickolaj
      Posted at 10:15 March 20, 2014
      Nickolaj
      Reply
      Author

      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

  • Adrian Szwejkowski
    Posted at 19:03 November 3, 2014
    Adrian Szwejkowski
    Reply
    Author

    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

    • Nickolaj
      Posted at 19:10 November 3, 2014
      Nickolaj
      Reply
      Author

      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

  • Scott
    Posted at 07:54 December 12, 2014
    Scott
    Reply
    Author

    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.

  • Scott Williams
    Posted at 19:18 March 12, 2015
    Scott Williams
    Reply
    Author

    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.

    • t_tt
      Posted at 18:27 March 25, 2015
      t_tt
      Reply
      Author

      I’m noticing the same thing.

      • t_tt
        Posted at 20:20 March 25, 2015
        t_tt
        Reply
        Author

        Did the same as Iain and it is now working. Thanks, Iain and Nickolaj!

  • Ian North
    Posted at 11:06 July 2, 2015
    Ian North
    Reply
    Author

    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.

  • Stephen Owen
    Posted at 17:57 October 2, 2015
    Stephen Owen
    Reply
    Author

    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

    • Nickolaj
      Posted at 13:12 October 5, 2015
      Nickolaj
      Reply
      Author

      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

      • Creed Cordonier
        Posted at 15:28 February 29, 2016
        Creed Cordonier
        Reply
        Author

        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!

  • Brian Moffet
    Posted at 22:33 February 5, 2016
    Brian Moffet
    Reply
    Author

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

  • Creed Cordonier
    Posted at 15:30 February 29, 2016
    Creed Cordonier
    Reply
    Author

    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

  • Jose “J.R.” Esposito
    Posted at 20:30 April 19, 2017
    Jose “J.R.” Esposito
    Reply
    Author

    Thanks – this helped me today.

  • Leave a Reply