Public/Import-VcConfigMgrApplication.ps1

function Import-VcConfigMgrApplication {
    <#
        .EXTERNALHELP VcRedist-help.xml
    #>

    [Alias("Import-VcCmApp")]
    [CmdletBinding(SupportsShouldProcess = $true, HelpURI = "https://vcredist.com/import-vcconfigmgrapplication/")]
    [OutputType([System.Management.Automation.PSObject])]
    param (
        [Parameter(
            Mandatory = $true,
            Position = 0,
            ValueFromPipeline = $true,
            HelpMessage = "Pass a VcList object from Save-VcRedist.")]
        [ValidateNotNullOrEmpty()]
        [System.Management.Automation.PSObject] $VcList,

        [Parameter(Mandatory = $false)]
        [System.ObsoleteAttribute("This parameter is not longer supported. The Path property must be on the object passed to -VcList.")]
        [System.String] $Path,

        [Parameter(Mandatory = $true, Position = 2)]
        [ValidateNotNullOrEmpty()]
        [System.String] $CMPath,

        [Parameter(Mandatory = $true, Position = 3)]
        [ValidateScript( { if ($_ -match "^[a-zA-Z0-9]{3}$") { $true } else { throw "$_ is not a valid ConfigMgr site code." } })]
        [System.String] $SMSSiteCode,

        [Parameter(Mandatory = $false, Position = 4)]
        [ValidatePattern("^[a-zA-Z0-9]+$")]
        [System.String] $AppFolder = "VcRedists",

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter] $Silent,

        [Parameter(Mandatory = $false)]
        [System.Management.Automation.SwitchParameter] $NoCopy,

        [Parameter(Mandatory = $false, Position = 5)]
        [ValidatePattern("^[a-zA-Z0-9]+$")]
        [System.String] $Publisher = "Microsoft",

        [Parameter(Mandatory = $false, Position = 6)]
        [ValidatePattern("^[a-zA-Z0-9\+ ]+$")]
        [System.String] $Keyword = "Visual C++ Redistributable"
    )

    begin {
        #region If the ConfigMgr console is installed, load the PowerShell module; Requires PowerShell module to be installed
        if (Test-Path -Path env:SMS_ADMIN_UI_PATH) {
            try {
                # Import the ConfigurationManager.psd1 module
                $params = @{
                    Path        = $(Split-Path -Path $env:SMS_ADMIN_UI_PATH -Parent)
                    Filter      = "ConfigurationManager.psd1"
                    ErrorAction = "SilentlyContinue"
                }
                $ModuleFile = Get-ChildItem @params
                if (-not[System.String]::IsNullOrEmpty($ModuleFile)) {
                    Write-Verbose -Message "Importing module: $($ModuleFile.FullName)"
                    Import-Module -Name $ModuleFile.FullName -Verbose:$false
                }
                else {
                    $Msg = "Could not load ConfigurationManager.psd1 from $(Split-Path -Path $env:SMS_ADMIN_UI_PATH -Parent). Please make sure that the Configuration Manager console is installed."
                    throw [System.IO.FileNotFoundException]::New($Msg)
                }
            }
            catch {
                throw $_
            }
        }
        else {
            $Msg = "Cannot find environment variable SMS_ADMIN_UI_PATH. Is the ConfigMgr console and PowerShell module installed?"
            throw [System.Exception]::New($Msg)
        }
        #endregion

        #region Validate $CMPath
        if (Resolve-Path -Path $CMPath) {
            $CMPath = $CMPath.TrimEnd("\")

            # Create the folder for importing the Redistributables into
            if ($AppFolder.Length -gt 0) {
                $DestCmFolder = "$($SMSSiteCode):\Application\$($AppFolder)"
                if ($PSCmdlet.ShouldProcess($DestCmFolder, "Creating")) {
                    Write-Verbose -Message "Creating: $DestCmFolder."
                    New-Item -Path $DestCmFolder -ErrorAction "SilentlyContinue" | Out-Null
                }
            }
            else {
                Write-Verbose -Message "Importing into: $($SMSSiteCode):\Application."
                $DestCmFolder = "$($SMSSiteCode):\Application"
            }
        }
        else {
            $Msg = "Unable to confirm '$CMPath' exists. Please check that '$CMPath' is valid."
            throw [System.IO.DirectoryNotFoundException]::New($Msg)
        }
        #endregion
    }

    process {

        # Make sure that $VcList has the required properties
        if ((Test-VcListObject -VcList $VcList) -ne $true) {
            $Msg = "Required properties not found. Please ensure the output from Save-VcRedist is sent to this function. "
            throw [System.Management.Automation.PropertyNotFoundException]::New($Msg)
        }

        foreach ($VcRedist in $VcList) {
            Write-Verbose -Message "Importing VcRedist app: 'Visual C++ Redistributable $($VcRedist.Release) $($VcRedist.Architecture) $($VcRedist.Version)'"

            # Import as an application into ConfigMgr
            if ($PSCmdlet.ShouldProcess("'$($VcRedist.Name)' in $CMPath", "Import ConfigMgr app")) {

                # Create the ConfigMgr application with properties from the manifest
                if ((Get-Item -Path $DestCmFolder).PSDrive.Name -eq $SMSSiteCode) {
                    if ($PSCmdlet.ShouldProcess($VcRedist.Name + " $($VcRedist.Architecture)", "Creating ConfigMgr application")) {

                        # Build paths
                        $SourceFolder = $(Split-Path -Path $VcRedist.Path -Parent)
                        $ContentLocation = [System.IO.Path]::Combine($CMPath, $VcRedist.Release, $VcRedist.Version, $VcRedist.Architecture)

                        #region Copy VcRedists to the network location. Use robocopy for robustness
                        if ($PSBoundParameters.ContainsKey($NoCopy)) {
                            Write-Warning -Message "NoCopy specified, skipping copy to $ContentLocation. Ensure VcRedists exist in the target."
                        }
                        else {
                            if ($PSCmdlet.ShouldProcess("'$($VcRedist.Path)' to '$($ContentLocation)'", "Copy")) {
                                if (!(Test-Path -Path $ContentLocation)) {
                                    New-Item -Path $ContentLocation -ItemType "Directory" -ErrorAction "SilentlyContinue" | Out-Null
                                }

                                try {
                                    $invokeProcessParams = @{
                                        FilePath     = "$env:SystemRoot\System32\robocopy.exe"
                                        ArgumentList = "`"$SourceFolder`" `"$ContentLocation`" `"$(Split-Path -Path $VcRedist.Path -Leaf)`" /S /XJ /R:1 /W:1 /NP /NJH /NJS /NFL /NDL"
                                    }
                                    Invoke-Process @invokeProcessParams | Out-Null
                                }
                                catch [System.Exception] {
                                    $Err = $_
                                    $Target = Join-Path -Path $ContentLocation -ChildPath $(Split-Path -Path $VcRedist.Path -Leaf)
                                    if (Test-Path -Path $Target) {
                                        Write-Verbose -Message "Copy successful: '$Target'."
                                    }
                                    else {
                                        Write-Warning -Message "Failed to copy Redistributables from '$($VcRedist.Path)' to '$ContentLocation'."
                                        Write-Warning -Message $Err.Exception.Message
                                    }
                                }
                            }
                        }
                        #endregion

                        # Change to the SMS Application folder before importing the applications
                        try {
                            Write-Verbose -Message "Setting location to: $DestCmFolder"
                            Push-Location -Path $DestCmFolder -ErrorAction "Continue"
                        }
                        catch [System.Exception] {
                            Pop-Location
                            Write-Warning -Message "Failed to set location to: $DestCmFolder."
                            Write-Warning -Message $_.Exception.Message
                        }

                        try {
                            # Splat New-CMApplication parameters, add the application and move into the target folder
                            $ApplicationName = "Visual C++ Redistributable $($VcRedist.Release) $($VcRedist.Architecture) $($VcRedist.Version)"
                            $cmAppParams = @{
                                Name              = $ApplicationName
                                Description       = "$Publisher $ApplicationName imported by $($MyInvocation.MyCommand). https://vcredist.com"
                                SoftwareVersion   = $VcRedist.Version
                                LinkText          = $VcRedist.URL
                                Publisher         = $Publisher
                                Keyword           = $Keyword
                                ReleaseDate       = $(Get-Date -Format (([System.Globalization.CultureInfo]::CurrentUICulture.DateTimeFormat).ShortDatePattern))
                                PrivacyUrl        = "https://go.microsoft.com/fwlink/?LinkId=521839"
                                UserDocumentation = "https://visualstudio.microsoft.com/vs/support/"
                            }
                            $app = New-CMApplication @cmAppParams
                            if ($AppFolder) {
                                $app | Move-CMObject -FolderPath $DestCmFolder -ErrorAction "SilentlyContinue" | Out-Null
                            }
                        }
                        catch [System.Exception] {
                            Pop-Location
                            Write-Warning -Message "Failed to create application $($VcRedist.Name) $($VcRedist.Architecture)."
                            Write-Warning -Message $_.Exception.Message
                        }
                        # Write app detail to the pipeline
                        Write-Output -InputObject $app
                    }

                    # Add a deployment type to the application
                    if ($PSCmdlet.ShouldProcess($("$($VcRedist.Name) $($VcRedist.Architecture) $($VcRedist.Version)"), "Adding deployment type")) {

                        # Change to the SMS Application folder before importing the applications
                        try {
                            Write-Verbose -Message "Set location to: $DestCmFolder"
                            Push-Location -Path $DestCmFolder -ErrorAction "Continue"
                        }
                        catch [System.Exception] {
                            Pop-Location
                            Write-Warning -Message "Failed to set location to: $DestCmFolder."
                            Write-Warning -Message $_.Exception.Message
                        }

                        try {
                            # Create the detection method
                            $params = @{
                                Hive    = "LocalMachine"
                                Is64Bit = if ($VcRedist.UninstallKey -eq "64") { $true } else { $false }
                                KeyName = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$($VcRedist.ProductCode)"
                            }
                            $detectionClause = New-CMDetectionClauseRegistryKey @params

                            # Splat Add-CMScriptDeploymentType parameters and add the application deployment type
                            $cmScriptParams = @{
                                ApplicationName          = $ApplicationName
                                InstallCommand           = "$(Split-Path -Path $VcRedist.Path -Leaf) $(if ($Silent) { $VcRedist.SilentInstall } else { $VcRedist.Install })"
                                ContentLocation          = $ContentLocation
                                AddDetectionClause       = $detectionClause
                                DeploymentTypeName       = "SCRIPT_$($VcRedist.Name)"
                                UserInteractionMode      = "Hidden"
                                UninstallCommand         = $VcRedist.SilentUninstall
                                LogonRequirementType     = "WhetherOrNotUserLoggedOn"
                                InstallationBehaviorType = "InstallForSystem"
                                Comment                  = "Generated by $($MyInvocation.MyCommand). https://vcredist.com"
                            }
                            Add-CMScriptDeploymentType @cmScriptParams | Out-Null
                        }
                        catch [System.Exception] {
                            Pop-Location
                            Write-Warning -Message "Failed to add script deployment type."
                            Write-Warning -Message $_.Exception.Message
                        }
                    }
                }
            }
        }
    }

    end {
        Pop-Location
    }
}