Public/Import-VcConfigMgrApplication.ps1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
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 "Continue" > $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.Contains($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 "Continue" > $null
                                }
                                try {
                                    $invokeProcessParams = @{
                                        FilePath     = "$env:SystemRoot\System32\robocopy.exe"
                                        ArgumentList = "$(Split-Path -Path $VcRedist.Path -Leaf) `"$SourceFolder`" `"$ContentLocation`" /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'."
                                        throw $Err
                                    }
                                }
                            }
                        }
                        #endregion

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

                        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" > $null
                            }
                        }
                        catch [System.Exception] {
                            Write-Warning -Message "Failed to create application $($VcRedist.Name) $($VcRedist.Architecture)."
                            throw $_
                        }
                        # 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"
                            Set-Location -Path $DestCmFolder -ErrorAction "Continue"
                        }
                        catch [System.Exception] {
                            Write-Warning -Message "Failed to set location to: $DestCmFolder."
                            throw $_
                        }

                        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 > $null
                        }
                        catch [System.Exception] {
                            Write-Warning -Message "Failed to add script deployment type."
                            throw $_
                        }
                    }
                }
            }
        }
    }

    end {
    }
}