Convert ConfigMgr Applications to MDT Applications with PowerShell

For those of you who have ever attended one of Johan Arwidmark’s talks or classes on OS deployment, you were probably convinced, like me, to capture your reference image using MDT LiteTouch for deployment with ConfigMgr. There are several advantages to capturing your reference image with LiteTouch (speed, compatibility, delegation, features) and there can also be a few disadvantages. One of those is if you have a thick or fat image, i.e. you have a lot of applications installed in your reference image.

Most of the applications you have in your MDT environment need to be maintained in your Configuration Manager environment as well, so that you can deploy the latest versions to your SCCM clients. So, when the time comes to update your reference image, you need to create all of those applications again in MDT.

Enter the \”Convert-CMApptoMDTApp\” PowerShell script. This script converts applications created in ConfigMgr 2012 SP1 to MDT applications. It utilises the new Configuration Manager 2012 SP1 and MDT 2012 PowerShell modules.

To create an application in MDT, we need a few bits of information:

Name, ShortName, Version, SoftwareVersion, Publisher, Language, CommmandLine, WorkingDirectory, ApplicationSourcePath, DestinationFolder

These can be retrieved from the the ConfigMgr application using the Get-CMDeploymentType cmdlet. We get the latest revisions by specifying the IsLatest property = True.

The Get-CMDeploymentType cmdlet is a strange fish and I had to play around with it quite a bit to understand the information it returns.

When you run the Get-CMDeploymentType cmdlet using the -ApplicationName parameter, you get back an array of deployment types. Each deployment type has a property called SDMPackageXML, which contains most of the interesting information that we need. Firstly, we need to convert this object from XML using the [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString() method so that we can easily access its properties.

Now, each SDMPackageXML object has a property called DeploymentTypes. If you have an application with one Deployment type, the DeploymentTypes property will return information about that one deployment type. However, things start to get a little confusing when you have multiple deployment types for an application.

Let me explain.

1. You have an Application called App1, with a deployment type DT1, which has a version of V1. You run the Get-CMDeploymentType cmdlet. It returns one deployment type. The DeploymentTypes property of that deployment types returns information about DT1 v1.

2. You add a second deployment type to App1, called DT2 v1. You run the Get-CMDeploymentType cmdlet. It returns two deployment types. The DeploymentTypes property of DT1 returns information about DT1. The DeploymentTypes property of DT2 returns information about DT1 v1 and DT2 v1.

3. You make a change to deployment type DT1 v1 – it is now v2. You run the Get-CMDeploymentType cmdlet. It returns two deployment types. The DeploymentTypes property of DT1 returns information about DT1 v2 and DT2 v1. The DeploymentTypes property of DT2 returns information about DT1 v1 and DT2 v1.

So, to summarise the behaviour of the cmdlet when there are multiple deployment types: The most recently modified deployment type contains the most up-to-date information about all deployment types for that application, while less recently modified deployment types contain information about themselves and the versions of other deployment types that existed when they were modified. Weird, huh?

So, to make sure we look at the most recent version of a deployment type in the DeploymentTypes property for that specific deployment type, we need to compare the ID and version of the deployment type using the CI_UniqueID property and the DeploymentTypes.ID property:

Compare-Object -ReferenceObject $dtUniqueID -DifferenceObject $dtInstance.ID -IncludeEqual -Property Name,Version | Where { $_.SideIndicator -eq "==" }

Another issue I encountered is that MDT won\’t create applications with the same name or destination folder, but multiple deployment types can share the same application name and different applications might have deployments types with the same name. So we need to append a number to each duplicate application name to ensure they are unique.

Here’s a nice one-liner that takes care of this problem:

$cmAppDT | Group-Object -Property Name -ErrorAction SilentlyContinue | Where { Foreach-object { $_.count -gt 1; $i=1} } -ErrorAction SilentlyContinue | Select Group -ExpandProperty Group -ErrorAction SilentlyContinue | Foreach-object { Add-Member -InputObject $_ -membertype NoteProperty -name Name -value ($_.Name + "_" + $i) -Force -ErrorAction SilentlyContinue; Add-Member -InputObject $_ -membertype NoteProperty -name DestinationFolder -value ($_.DestinationFolder + "_" + $i) -Force -ErrorAction SilentlyContinue; $i++ }

If you have two deployment types that share the same application name “Application”, these will be renamed Application_1 and Application_2.

Now that we have those small details out of the way, let\’s see the script in action.

The script will get a list of all of your CM12 applications and display the list using the Out-GridView cmdlet. This lets you select the applications you want to convert.

\"CMApps\"

Once you have selected the applications for conversion, the script runs the Get-CMDeploymentType cmdlet against each one and displays a progress indicator. This can take some time, depending on how many applications you have selected.

\"DT1\"

It will only look at MSI and Script deployment types, extract all the necessary information to create an application in MDT and then display the list of deployment types, again using the Out-GridView cmdlet. This lets you select the deployment types you want to convert.

\"MDTApps\"

Once you have selected the deployment types for conversion, they are passed to the Import-MDTApplication cmdlet to be created in MDT.

\"MDTImport\"

Script Pre-requisites:

Appropriate permissions in ConfigMgr and MDT.
The Configuration Manager 2012 SP1 PowerShell Module is expected in this directory:
\”C:\\Program Files (x86)\\Microsoft Configuration Manager\\AdminConsole\\bin\\ConfigurationManager.psd1\”
The MDT 2012 PowerShell Module is expected in this directory:
\”C:\\Program Files\\Microsoft Deployment Toolkit\\bin\\MicrosoftDeploymentToolkit.psd1\”

Running the script:

To run the script, save it as \”Convert-CMApptoMDTApp.ps1\”. Launch a PowerShell (x86) console as administrator. You can set the values of the parameters in the script, or you can run the script with parameters, e.g.

Convert-CMApptoMDTApp.ps1 -CMSiteCode \”CM1\” -CMSiteServer \”CMServer1\” -MDTSharePath \”\\\\MDTServer1\\MDTShare\” -MDTAppFolderName \”CMApps\”

The script:

Param (
    [ValidateNotNullOrEmpty()]
    [string]$CMSiteCode = "CM1",
    [ValidateNotNullOrEmpty()]
    [string]$CMSiteServer = "CMServer",
    [ValidateNotNullOrEmpty()]
    [string]$MDTSharePath = "\\\\MDTServer\\MDTShare",
    [ValidateNotNullOrEmpty()]
    [string]$MDTAppFolderName = "CMApps"
)

# Import CM12 and MDT 2012 PowerShell modules
Import-Module "C:\\Program Files (x86)\\Microsoft Configuration Manager\\AdminConsole\\bin\\ConfigurationManager.psd1"
Import-Module "C:\\Program Files\\Microsoft Deployment Toolkit\\bin\\MicrosoftDeploymentToolkit.psd1"

# Set the working directory to the CM Site code
CD ($cmSiteCode + ":")

# Get a list of all CM applications
$cmApps = Get-WmiObject -ComputerName $cmSiteServer -Namespace "Root\\SMS\\Site_$cmSiteCode" -Query "Select LocalizedDisplayName from SMS_Application Where IsLatest=\'True\'" | Sort LocalizedDisplayName
# Output the list of applications to a Grid to allow browsing and selecting of the CM Apps to be converted to MDT Apps.
$cmApps = $cmApps | Out-GridView -PassThru

# Array to hold all of the CM deployment types
$cmAppDT = @()
# Counter for the progress of CM App processing.
$cmAppIndicator = 0
# Counter for the progress of conversion to MDT Apps.
$mdtAppIndicator = 0

If ($cmApps -ne $null) {
    Foreach ($cmApp in $cmApps) {
        $cmAppsCount = @($cmApps).Count
        $cmAppIndicator++
        Write-Progress -Activity "Processing Application $cmAppIndicator of $cmAppsCount" -Status $cmApp.LocalizedDisplayName -PercentComplete ($cmAppIndicator / $cmAppsCount * 100)

        # Get a list of the deployment types for each application
        $cmDeploymentType = Get-CMDeploymentType -ApplicationName ($cmApp | Select LocalizedDisplayName -ExpandProperty LocalizedDisplayName)

        # Enumerate the latest deployment types and get the latest SDMPackageVersion
        Foreach ($dt in $cmDeploymentType | Where { $_.IsLatest -eq $true }) {
            $SDMPackageXML = $dt | Select SDMPackageXML -ExpandProperty SDMPackageXML
            If ($dt.Technology -match "Script" -or $dt.Technology -match "MSI") {
                If ($SDMPackageXML -ne "") {
                    $dtInfo = [Microsoft.ConfigurationManagement.ApplicationManagement.Serialization.SccmSerializer]::DeserializeFromString($SDMPackageXML)
                    $dtCI_UniqueID = $dt.CI_UniqueID -split "/"
                    $dtUniqueID = @()
                    # Get the Deployment Type ID and version
                    $dtUniqueID = New-Object PSObject -Property @{
                        Name    = $dtCI_UniqueID[1]
                        Version = $dtCI_UniqueID[2]
                    }
                    $dtInstances = $dtInfo.DeploymentTypes
                    Foreach ($dtInstance in $dtInstances) {
                        # Compare the DT ID and Version to those contained in the DeploymentTypes property to make sure we get the most recent version of the deployment type and only this deployment type in our ForEach loop.
                        If ((Compare-Object -ReferenceObject $dtUniqueID -DifferenceObject $dtInstance.ID -IncludeEqual -Property Name, Version | Where { $_.SideIndicator -eq "==" } ) -ne $null ) {
                            $dtInstaller = $dtInstance | Select Installer -ExpandProperty Installer
                            If ($dtInstaller.Technology -match "Script" -or $dtInstaller.Technology -match "MSI") {
                                # If the working directory of the CM App is a local drive or environment variable, set the MDT App working directory accordingly
                                If ($dtInstaller.InstallFolder -match ":" -or $dtInstaller.InstallFolder -match "%") {
                                    $dtWorkingDirectory = $dtInstaller.InstallFolder
                                }
                                # Otherwise, set the MDT working directory to the root of the MDT application we are creating.
                                Else {
                                    $dtWorkingDirectory = ".\\Applications\\" + $dtInfo.Title
                                }
                                # Create a custom PS object with the information from the CM App DT we need to create the MDT App
                                $cmAppDT += New-Object PSObject -Property @{
                                    Name                  = $dtInfo.Title
                                    ShortName             = $dtInstance.Title
                                    Version               = $dtInfo.SoftwareVersion
                                    Publisher             = $dtInfo.Publisher
                                    Language              = ($dtInstance | Select Languages -ExpandProperty Languages) -join ","
                                    CommandLine           = $dtInstaller.InstallCommandLine
                                    WorkingDirectory      = $dtWorkingDirectory
                                    ApplicationSourcePath = ($dtInstaller | Select Contents -ExpandProperty Contents | Select Location -ExpandProperty Location)
                                    DestinationFolder     = $dtInfo.Title
                                }
                            }
                        }
                    }
                }
                Else {
                    $dtName = $dt.LocalizedDisplayName
                    Write-Host "$dtName has no SDMPackage information"
                }
            }
        }
    }

    If ($cmAppDT -ne $null) {

        # Multiple deployment types can share the same application name and different applications might have deployments types with the same name.
        # MDT won\'t allow applications with the same name or destination folder, so we need to append a number to each duplicate deployment type to ensure they are unique.
        $cmAppDT | Group-Object -Property Name -ErrorAction SilentlyContinue | Where { Foreach-object { $_.count -gt 1; $i = 1 } } -ErrorAction SilentlyContinue | Select Group -ExpandProperty Group -ErrorAction SilentlyContinue | Foreach-object { Add-Member -InputObject $_ -membertype NoteProperty -name Name -value ($_.Name + "_" + $i) -Force -ErrorAction SilentlyContinue; Add-Member -InputObject $_ -membertype NoteProperty -name DestinationFolder -value ($_.DestinationFolder + "_" + $i) -Force -ErrorAction SilentlyContinue; $i++ }

        # Output the Deployment Types to a Grid to allow browsing and selecting of the CM Apps to be converted to MDT Apps.
        $cmAppDTsToConvertToMDTApps = $cmAppDT | Sort -Property Name | Out-GridView -PassThru

        If ($cmAppDTsToConvertToMDTApps -ne $null) {
            $mdtAppCount = @($cmAppDTsToConvertToMDTApps).Count
            # Create a new PSDrive pointing to the MDT share
            New-PSDrive -Name "DS001" -PSProvider MDTProvider -Root $mdtSharePath | Out-Null
            # Create a folder under the MDT Applications folder for our imported CM applications.
            If (!(Test-Path (Join-Path "DS001:\\Applications" $MDTAppFolderName))) {
                New-Item -path "DS001:\\Applications" -Enable "True" -Name $MDTAppFolderName -Comments "Applications Converted From System Center Configuration Manager" -ItemType Folder | Out-Null
            }
            # Import each selected application in to MDT
            Foreach ($mdtApp in $cmAppDTsToConvertToMDTApps ) {
                $mdtAppIndicator++
                Write-Progress -Activity "Processing Application $mdtAppIndicator of $mdtAppCount" -Status $mdtApp.Name -PercentComplete ($mdtAppIndicator / $mdtAppCount * 100)
                Import-MDTApplication -path "DS001:\\Applications\\$MDTAppFolderName" -enable "True" -Name $mdtApp.Name -ShortName $mdtApp.ShortName -Version $mdtApp.Version -Publisher $mdtApp.Publisher -Language $mdtApp.Language -CommandLine $mdtApp.CommandLine -WorkingDirectory $mdtApp.WorkingDirectory -ApplicationSourcePath $mdtApp.ApplicationSourcePath -DestinationFolder $mdtApp.DestinationFolder
            }
        }
    }
}

6 thoughts on “Convert ConfigMgr Applications to MDT Applications with PowerShell”

  1. Pingback: Convert ConfigMgr Applications to MDT Applications with PowerShell | Deployment PT

    1. Hi

      Am i correct in interpreting the above, that this ONLY works with MSI Applications and does not work with standard Packages?

      If so, that is a major disappointment and oversight! Still the vast majority of software is exe based, not MSI.

      1. Hi Mike,

        You are correct in saying that the script converts applications, not packages. However, you seem to be under the impression that you need to use a package to deploy an exe, which isn’t the case.

        In the CM12 application model, you deploy an exe application using a ‘script’ deployment type. The script posted above will convert both CM12 MSI and Script (exe) applications to MDT applications.

        If you’re still using packages in CM12, I highly recommend looking at using applications instead. Microsoft have provided the Package Conversion Manager to convert packages to the applications.

        Seán

  2. Excellent script ! I have been working on one similar and can therefore appreciate the effort and frustrations you would have experienced.

  3. This has been great thanks! It might be worth mentioning that the Pre-Req’s require MDT to be installed ideally, rather than just the script. When running with just the script there were errors for Microsoft.BDD.PSSnapin errors. When MDT was installed it worked perfectly.

  4. Great Script, however, for many applications in our environment the SDMPackageXML property appears empty…even though there’s valid deployment type with respective configuration (accessible through CM console).

    I did pickup on Adam Meltzer comment “You need to “Get” the instance a second time to populate the lazy values. Lazy properties are an optimization to prevent unnecessary database load for large or expensive data sets.” (Article: https://social.technet.microsoft.com/Forums/en-US/6b04e1d0-e2e1-42f0-bb18-1643828feea3/sccm2012-sp1-powershell-get-all-content-locations?forum=configmanagersdk). I tried modifying the code to reflect this…no joy.

    Any thoughts, work arounds, welcome.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top