Public/Save-VcRedist.ps1

Function Save-VcRedist {
    <#
        .SYNOPSIS
            Downloads the Visual C++ Redistributables from an array returned by Get-VcXml.

        .DESCRIPTION
            Downloads the Visual C++ Redistributables from an array returned by Get-VcXml into a folder structure that represents release and processor architecture.
            If the redistributable exists in the specified path, it will not be re-downloaded.

        .OUTPUTS
            System.Array

        .NOTES
            Author: Aaron Parker
            Twitter: @stealthpuppy

        .LINK
            https://docs.stealthpuppy.com/docs/vcredist/usage/downloading-the-redistributables

        .PARAMETER VcList
            Sepcifies the array that lists the Visual C++ Redistributables to download

        .PARAMETER Path
            Specify a target folder to download the Redistributables to, otherwise use the current folder.

        .PARAMETER ForceWebRequest
            Forces the use of Invoke-WebRequest over Start-BitsTransfer

        .EXAMPLE
            Save-VcRedist -VcList (Get-VcList) -Path C:\Redist

            Description:
            Downloads the supported Visual C++ Redistributables to C:\Redist.
            
        .EXAMPLE
            Get-VcList | Save-VcRedist -Path C:\Redist -ForceWebRequest

            Description:
            Passes the list of supported Visual C++ Redistributables to Save-VcRedist and uses Invoke-WebRequest to download the Redistributables to C:\Redist.

        .EXAMPLE
            $VcList = Get-VcList -Release 2013, 2019 -Architecture x86
            Save-VcRedist -VcList $VcList -Path C:\Redist -ForceWebRequest

            Description:
            Passes the list of 2013 and 2019 x86 supported Visual C++ Redistributables to Save-VcRedist and uses Invoke-WebRequest to download the Redistributables to C:\Redist.
    #>

    [Alias("Get-VcRedist")]
    [CmdletBinding(SupportsShouldProcess = $True, HelpURI = "https://docs.stealthpuppy.com/docs/vcredist/usage/downloading-the-redistributables")]
    [OutputType([System.Management.Automation.PSObject])]
    Param (
        [Parameter(Mandatory = $True, Position = 0, ValueFromPipeline)]
        [ValidateNotNull()]
        [System.Management.Automation.PSObject] $VcList,

        [Parameter(Mandatory = $False, Position = 1)]
        [ValidateScript( { If (Test-Path $_ -PathType 'Container') { $True } Else { Throw "Cannot find path $_" } })]
        [System.String] $Path,

        [Parameter(Mandatory = $False)]
        [System.Management.Automation.SwitchParameter] $ForceWebRequest,

        [Parameter(Mandatory = $False, Position = 2)]
        [ValidateSet('Foreground', 'High', 'Normal', 'Low')]
        [System.String] $Priority = "Foreground",

        [Parameter(Mandatory = $False, Position = 3)]
        [System.String] $Proxy,

        [Parameter(Mandatory = $False, Position = 4)]
        [System.Management.Automation.PSCredential]
        $ProxyCredential = [System.Management.Automation.PSCredential]::Empty
    )

    # Loop through each Redistributable and download to the target path
    ForEach ($Vc in $VcList) {
        Write-Verbose -Message "$($MyInvocation.MyCommand): Download: [$($Vc.Name), $($Vc.Release), $($Vc.Architecture)]"

        # Create the folder to store the downloaded file. Skip if it exists
        $folder = Join-Path (Join-Path (Join-Path $(Resolve-Path -Path $Path) $Vc.Release) $Vc.Architecture) $Vc.ShortName
        If (Test-Path -Path $folder) {
            Write-Verbose -Message "$($MyInvocation.MyCommand): Folder '$folder' exists. Skipping."
        }
        Else {
            If ($pscmdlet.ShouldProcess($folder, "Create")) {
                try {
                    New-Item -Path $folder -Type Directory -Force -ErrorAction SilentlyContinue | Out-Null
                }
                catch [System.Exception] {
                    Write-Warning -Message "$($MyInvocation.MyCommand): Failed to create folder: [$folder]."
                    Throw $_.Exception.Message
                    Continue
                }
            }
        }
            
        # Test whether the VcRedist is already on disk
        $target = Join-Path $folder $(Split-Path -Path $Vc.Download -Leaf)
        Write-Verbose -Message "$($MyInvocation.MyCommand): Testing target: $($target)"

        If (Test-Path -Path $target -PathType Leaf) {
            $ProductVersion = $(Get-FileMetadata -Path $target).ProductVersion
                
            # If the target Redistributable is already downloaded, compare the version
            If (($Vc.Version -gt $ProductVersion) -or ($Null -eq $ProductVersion)) {
                # Download the newer version
                Write-Verbose -Message "$($MyInvocation.MyCommand): $($Vc.Version) > $ProductVersion."
                $download = $True
            }
            Else {
                Write-Verbose -Message "$($MyInvocation.MyCommand): Manifest version: $($Vc.Version) matches file version: $ProductVersion."
                $download = $False
            }
        }
        Else {
            $download = $True
        }

        # The VcRedist needs to be downloaded
        If ($download) {

            # If -ForceWebRequest or running on PowerShell Core (or Start-BitsTransfer is unavailable) download with Invoke-WebRequest
            If ($ForceWebRequest -or (Test-PSCore)) {
                If ($pscmdlet.ShouldProcess($Vc.Download, "WebDownload")) {

                    # Use Invoke-WebRequest in instances where Start-BitsTransfer isn't supported or won't work
                    try {
                        $iwrParams = @{
                            Uri             = $Vc.Download
                            OutFile         = $target
                            UseBasicParsing = $True
                            ErrorAction     = "SilentlyContinue"
                        }
                        If ($PSBoundParameters.ContainsKey('Proxy')) {
                            $iwrParams.Proxy = $Proxy
                        }
                        If ($PSBoundParameters.ContainsKey('ProxyCredential')) {
                            $iwrParams.ProxyCredentials = $ProxyCredential
                        }
                        Invoke-WebRequest @iwrParams
                    }
                    catch [System.Net.Http.HttpRequestException] {
                        Write-Warning -Message "$($MyInvocation.MyCommand): HttpRequestException: Check URL is valid: [$($Vc.Download)]."
                        Throw $_.Exception.Message
                        Continue
                    }
                    catch [System.Net.WebException] {
                        Write-Warning -Message "$($MyInvocation.MyCommand): WebException."
                        Throw $_.Exception.Message
                        Continue
                    }
                    catch [System.Exception] {
                        Write-Warning -Message "$($MyInvocation.MyCommand): Failed to download VcRedist from: [$($Vc.Download)]."
                        Throw $_.Exception.Message
                        Continue
                    }
                }
            }
            Else {
                If ($pscmdlet.ShouldProcess($Vc.Download, "BitsDownload")) {
                        
                    # Use Start-BitsTransfer
                    try {
                        $sbtParams = @{
                            Source      = $Vc.Download
                            Destination = $target
                            Priority    = $Priority
                            DisplayName = "Visual C++ Redistributable Download"
                            Description = $Vc.Name
                            ErrorAction = "SilentlyContinue"
                        }
                        If ($PSBoundParameters.ContainsKey('Proxy')) {
                            # Set priority to Foreground because the proxy will remove the Range protocol header
                            $sbtParams.Priority = "Foreground"
                            $sbtParams.ProxyUsage = "Override"
                            $sbtParams.ProxyList = $Proxy
                        }
                        If ($PSBoundParameters.ContainsKey('ProxyCredential')) {
                            $sbtParams.ProxyCredential = $ProxyCredentials
                        }
                        Start-BitsTransfer @sbtParams
                    }
                    catch [System.Exception] {
                        Write-Warning -Message "$($MyInvocation.MyCommand): Failed to download VcRedist from: [$($Vc.Download)]."
                        Throw $_.Exception.Message
                        Continue
                    }
                }
            }
        }
        Else {
            Write-Verbose -Message "$($MyInvocation.MyCommand): $($target) exists."
        }
    }

    # Return the $VcList array on the pipeline so that we can act on what was downloaded
    Write-Output -InputObject $filteredVcList
}