private async Task <DownloadedAsset> DownloadBlobAsync(HttpClient client,
                                                               Build build,
                                                               Asset asset,
                                                               AssetLocation assetLocation,
                                                               string subPath,
                                                               List <string> errors)
        {
            // Normalize the asset name.  Sometimes the upload to the BAR will have
            // "assets/" prepended to it and sometimes not (depending on the Maestro tasks version).
            // Remove assets/ if it exists so we get consistent target paths.
            string normalizedAssetName = asset.Name;

            if (asset.Name.StartsWith("assets/"))
            {
                normalizedAssetName = asset.Name.Substring("assets/".Length);
            }

            string fullTargetPath = Path.Combine(subPath, assetsSubPath, normalizedAssetName);

            DownloadedAsset downloadedAsset = new DownloadedAsset()
            {
                Successful     = false,
                Asset          = asset,
                TargetLocation = fullTargetPath
            };

            // If the location is a blob storage account ending in index.json, as would be expected
            // if PushToBlobFeed was used, strip off the index.json and append the asset name. If that doesn't work,
            // prepend "assets/" to the asset name and try that.
            // When uploading assets via the PushToBlobFeed task, assets/ may be prepended (e.g. assets/symbols/)
            // upon upload, but may not be reported to the BAR, or may be reported to BAR.
            // Either way, normalize so that we end up with only one assets/ prepended.

            if (IsBlobFeedUrl(assetLocation.Location))
            {
                string finalBaseUri = assetLocation.Location.Substring(0, assetLocation.Location.Length - "index.json".Length);
                string finalUri1    = $"{finalBaseUri}{asset.Name}";
                string finalUri2    = $"{finalBaseUri}assets/{asset.Name}";
                if (await DownloadFileAsync(client, finalUri1, fullTargetPath, errors))
                {
                    downloadedAsset.Successful     = true;
                    downloadedAsset.SourceLocation = finalUri1;
                    return(downloadedAsset);
                }
                if (await DownloadFileAsync(client, finalUri2, fullTargetPath, errors))
                {
                    downloadedAsset.Successful     = true;
                    downloadedAsset.SourceLocation = finalUri2;
                    return(downloadedAsset);
                }
                // Could be under assets/assets/ in some recent builds due to a bug in the release
                // pipeline.
                if (!_options.NoWorkarounds)
                {
                    string finalUri3 = $"{finalBaseUri}assets/assets/{asset.Name}";
                    if (await DownloadFileAsync(client, finalUri3, fullTargetPath, errors))
                    {
                        downloadedAsset.Successful     = true;
                        downloadedAsset.SourceLocation = finalUri3;
                        return(downloadedAsset);
                    }
                }
                return(downloadedAsset);
            }
            else if (IsAzureDevOpsArtifactsUrl(assetLocation.Location))
            {
                // Do not attempt to download from AzDO.
                return(downloadedAsset);
            }

            if (string.IsNullOrEmpty(assetLocation.Location))
            {
                errors.Add($"Asset location for {asset.Name} is not available.");
            }
            else
            {
                errors.Add($"Blob uri '{assetLocation.Location} for {asset.Name} is of an unknown type");
            }
            return(downloadedAsset);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="client"></param>
        /// <param name="asset"></param>
        /// <param name="rootOutputDirectory"></param>
        /// <returns></returns>
        /// <remarks>
        ///     Layout:
        ///     {root dir}\shipping\assets - blobs
        ///     {root dir}\shipping\packages - packages
        ///     {root dir}\nonshipping\assets - blobs
        ///     {root dir}\nonshipping\packages - packages
        /// </remarks>
        private async Task <DownloadedAsset> DownloadAssetAsync(HttpClient client,
                                                                Build build,
                                                                Asset asset,
                                                                string rootOutputDirectory)
        {
            string assetNameAndVersion = GetAssetNameForLogging(asset);

            if (_options.IncludeNonShipping || !asset.NonShipping)
            {
                Console.WriteLine($"  Downloading asset {assetNameAndVersion}");
            }
            else
            {
                Console.WriteLine($"  Skipping non-shipping asset {assetNameAndVersion}");
                return(null);
            }

            DownloadedAsset downloadedAsset = new DownloadedAsset()
            {
                Successful = false,
                Asset      = asset
            };

            List <string> errors = new List <string>();

            List <AssetLocation> assetLocations = new List <AssetLocation>(asset.Locations);

            if (assetLocations.Count == 0)
            {
                // If there is no location information and the user wants workarounds, add a bunch
                // of feeds.
                if (!_options.NoWorkarounds)
                {
                    if (asset.Name.Contains("/"))
                    {
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetcli.blob.core.windows.net/dotnet/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetclichecksums.blob.core.windows.net/dotnet/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json"));
                    }
                    else
                    {
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json"));
                    }
                }
                else
                {
                    errors.Add($"Asset '{assetNameAndVersion}' has no known location information.");
                }
            }

            if (assetLocations.Count != 0)
            {
                string subPath = Path.Combine(rootOutputDirectory, asset.NonShipping ? nonShippingSubPath : shippingSubPath);

                // Walk the locations and attempt to gather the asset at each one, setting the output
                // path based on the type.
                foreach (AssetLocation location in assetLocations)
                {
                    var locationType = location.Type;

                    // Make sure the location type ends up correct. Currently in some cases,
                    // we end up with 'none' or a symbol package ends up with nuget feed.
                    // Generally we can make an assumption that if the path doesn't have a
                    // '/' then it's a package.  Nuget packages also don't have '.nupkg' suffix
                    // (they are just the package name).
                    if (!_options.NoWorkarounds)
                    {
                        if (!asset.Name.Contains("/") && !asset.Name.Contains(".nupkg"))
                        {
                            locationType = LocationType.NugetFeed;
                        }
                        else
                        {
                            locationType = LocationType.Container;
                        }
                    }

                    switch (locationType)
                    {
                    case LocationType.NugetFeed:
                        downloadedAsset = await DownloadNugetPackageAsync(client, build, asset, location, subPath, errors);

                        break;

                    case LocationType.Container:
                        downloadedAsset = await DownloadBlobAsync(client, build, asset, location, subPath, errors);

                        break;

                    case LocationType.None:
                    default:
                        errors.Add($"Unexpected location type {locationType}");
                        break;
                    }

                    if (downloadedAsset.Successful)
                    {
                        return(downloadedAsset);
                    }
                }
            }

            // If none of the download attempts succeeded, then we should print out all the error
            // information.
            Console.WriteLine($"    Failed to download asset, errors shown below:");
            foreach (string error in errors)
            {
                Console.WriteLine($"      {error}");
            }

            return(downloadedAsset);
        }
        /// <summary>
        ///     Gather the drop for a specific build.
        /// </summary>
        /// <param name="build">Build to gather drop for</param>
        /// <param name="rootOutputDirectory">Output directory. Must exist.</param>
        private async Task <DownloadedBuild> GatherDropForBuildAsync(Build build, string rootOutputDirectory)
        {
            IRemote remote  = RemoteFactory.GetBarOnlyRemote(_options, Logger);
            bool    success = true;

            // If the drop is separated, calculate the directory name based on the last element of the build
            // repo uri plus the build number (to disambiguate overlapping builds)
            string outputDirectory = rootOutputDirectory;
            string repoUri         = build.GitHubRepository ?? build.AzureDevOpsRepository;

            if (_options.ReleaseLayout)
            {
                int lastSlash = repoUri.LastIndexOf("/");
                if (lastSlash != -1 && lastSlash != repoUri.Length - 1)
                {
                    outputDirectory = Path.Combine(rootOutputDirectory, repoUri.Substring(lastSlash + 1), build.AzureDevOpsBuildNumber);
                }
                else
                {
                    // Might contain invalid path chars, this is currently unhandled.
                    outputDirectory = Path.Combine(rootOutputDirectory, repoUri, build.AzureDevOpsBuildNumber);
                }
                Directory.CreateDirectory(outputDirectory);
            }

            List <DownloadedAsset> downloadedAssets = new List <DownloadedAsset>();
            bool anyShipping = false;

            Console.WriteLine($"Gathering drop for build {build.AzureDevOpsBuildNumber} of {repoUri}");
            using (HttpClient client = new HttpClient(new HttpClientHandler {
                CheckCertificateRevocationList = true
            }))
            {
                var assets = await remote.GetAssetsAsync(buildId : build.Id, nonShipping : (!_options.IncludeNonShipping ? (bool?)false : null));

                foreach (var asset in assets)
                {
                    DownloadedAsset downloadedAsset = await DownloadAssetAsync(client, build, asset, outputDirectory);

                    if (downloadedAsset == null)
                    {
                        continue;
                    }
                    else if (!downloadedAsset.Successful)
                    {
                        success = false;
                        if (!_options.ContinueOnError)
                        {
                            Console.WriteLine($"Aborting download.");
                            break;
                        }
                    }
                    else
                    {
                        anyShipping |= !asset.NonShipping;
                        downloadedAssets.Add(downloadedAsset);
                    }
                }
            }

            DownloadedBuild newBuild = new DownloadedBuild
            {
                Successful        = success,
                Build             = build,
                DownloadedAssets  = downloadedAssets,
                OutputDirectory   = outputDirectory,
                AnyShippingAssets = anyShipping
            };

            // If separated drop, generate a manifest per build
            if (_options.ReleaseLayout)
            {
                await WriteDropManifest(new List <DownloadedBuild>() { newBuild }, outputDirectory);
            }

            return(newBuild);
        }
        private async Task <DownloadedAsset> DownloadBlobAsync(HttpClient client,
                                                               Build build,
                                                               Asset asset,
                                                               AssetLocation assetLocation,
                                                               string subPath,
                                                               List <string> errors)
        {
            // Normalize the asset name.  Sometimes the upload to the BAR will have
            // "assets/" prepended to it and sometimes not (depending on the Maestro tasks version).
            // Remove assets/ if it exists so we get consistent target paths.
            string normalizedAssetName = asset.Name;

            if (asset.Name.StartsWith("assets/"))
            {
                normalizedAssetName = asset.Name.Substring("assets/".Length);
            }

            string fullTargetPath = Path.Combine(subPath, assetsSubPath, normalizedAssetName);

            DownloadedAsset downloadedAsset = new DownloadedAsset()
            {
                Successful     = false,
                Asset          = asset,
                TargetLocation = fullTargetPath
            };

            // If the location is a blob storage account ending in index.json, as would be expected
            // if PushToBlobFeed was used, strip off the index.json and append the asset name. If that doesn't work,
            // prepend "assets/" to the asset name and try that.
            // When uploading assets via the PushToBlobFeed task, assets/ may be prepended (e.g. assets/symbols/)
            // upon upload, but may not be reported to the BAR, or may be reported to BAR.
            // Either way, normalize so that we end up with only one assets/ prepended.

            if (IsBlobFeedUrl(assetLocation.Location))
            {
                string finalBaseUri = assetLocation.Location.Substring(0, assetLocation.Location.Length - "index.json".Length);
                string finalUri1    = $"{finalBaseUri}{asset.Name}";
                string finalUri2    = $"{finalBaseUri}assets/{asset.Name}";
                if (await DownloadFileAsync(client, finalUri1, fullTargetPath, errors))
                {
                    downloadedAsset.Successful     = true;
                    downloadedAsset.SourceLocation = finalUri1;
                    return(downloadedAsset);
                }
                if (await DownloadFileAsync(client, finalUri2, fullTargetPath, errors))
                {
                    downloadedAsset.Successful     = true;
                    downloadedAsset.SourceLocation = finalUri2;
                    return(downloadedAsset);
                }
                return(downloadedAsset);
            }
            // WORKAROUND: Right now we don't have the ability to have multiple root build locations
            // So the BAR location gets reported as the overall manifest location.  This isn't correct,
            // but we're stuck with it for now until we redesign how the manifest merging is done.
            // So if we see a myget url here, just look up the asset in the dotnetcli storage account.
            if (IsMyGetUrl(assetLocation.Location) && assetLocation.Location.Contains("aspnetcore-dev"))
            {
                // First try to grab the asset from the dotnetcli storage account
                string dotnetcliStorageUri = $"https://dotnetcli.blob.core.windows.net/dotnet/{asset.Name}";
                if (await DownloadFileAsync(client, $"{dotnetcliStorageUri}", fullTargetPath, errors))
                {
                    downloadedAsset.Successful     = true;
                    downloadedAsset.SourceLocation = dotnetcliStorageUri;
                    return(downloadedAsset);
                }
                // AspNet symbol packages have incorrect names right now.  They are found on the drop share.
                if (asset.Name.EndsWith(".symbols.nupkg"))
                {
                    string symbolPackageName = asset.Name;
                    int    lastSlash         = asset.Name.LastIndexOf("/");
                    if (lastSlash != -1)
                    {
                        symbolPackageName = asset.Name.Substring(lastSlash);
                    }
                    string shippingNonShippingFolder = asset.NonShipping ? "NonShipping" : "Shipping";
                    string aspnetciSymbolSharePath   = $@"\\aspnetci\drops\AspNetCore\master\{build.AzureDevOpsBuildNumber}\packages\Release\{shippingNonShippingFolder}\{symbolPackageName}";
                    if (await DownloadFromShareAsync(aspnetciSymbolSharePath, fullTargetPath, errors))
                    {
                        downloadedAsset.Successful     = true;
                        downloadedAsset.SourceLocation = aspnetciSymbolSharePath;
                        return(downloadedAsset);
                    }
                }
                return(downloadedAsset);
            }
            else
            {
                if (string.IsNullOrEmpty(assetLocation.Location))
                {
                    errors.Add($"Asset location for {asset.Name} is not available.");
                }
                else
                {
                    errors.Add($"Blob uri '{assetLocation.Location} for {asset.Name} is of an unknown type");
                }
                return(downloadedAsset);
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="client"></param>
        /// <param name="asset"></param>
        /// <param name="rootOutputDirectory"></param>
        /// <returns></returns>
        /// <remarks>
        ///     Layout:
        ///     {root dir}\shipping\assets - blobs
        ///     {root dir}\shipping\packages - packages
        ///     {root dir}\nonshipping\assets - blobs
        ///     {root dir}\nonshipping\packages - packages
        /// </remarks>
        private async Task <DownloadedAsset> DownloadAssetAsync(HttpClient client,
                                                                Build build,
                                                                Asset asset,
                                                                string rootOutputDirectory)
        {
            string assetNameAndVersion = GetAssetNameForLogging(asset);

            if (_options.IncludeNonShipping || !asset.NonShipping)
            {
                Console.WriteLine($"  Downloading asset {assetNameAndVersion}");
            }
            else
            {
                Console.WriteLine($"  Skipping non-shipping asset {assetNameAndVersion}");
                return(null);
            }

            DownloadedAsset downloadedAsset = new DownloadedAsset()
            {
                Successful = false,
                Asset      = asset
            };
            List <string> errors = new List <string>();

            if (asset.Locations.Count == 0)
            {
                errors.Add($"Asset '{assetNameAndVersion}' has no known location information.");
            }
            else
            {
                string subPath = Path.Combine(rootOutputDirectory, asset.NonShipping ? nonShippingSubPath : shippingSubPath);

                // Walk the locations and attempt to gather the asset at each one, setting the output
                // path based on the type. Note that if there are multiple locations and their types don't
                // match, consider this an error.
                LocationType locationType = asset.Locations[0].Type;
                foreach (AssetLocation location in asset.Locations)
                {
                    if (locationType != location.Type)
                    {
                        errors.Add($"Asset '{assetNameAndVersion}' has inconsistent location types ({locationType} vs. {location.Type})");
                        break;
                    }

                    switch (locationType)
                    {
                    case LocationType.NugetFeed:
                        downloadedAsset = await DownloadNugetPackageAsync(client, build, asset, location, subPath, errors);

                        break;

                    case LocationType.Container:
                        downloadedAsset = await DownloadBlobAsync(client, build, asset, location, subPath, errors);

                        break;

                    default:
                        errors.Add($"Unexpected location type {locationType}");
                        break;
                    }

                    if (downloadedAsset.Successful)
                    {
                        return(downloadedAsset);
                    }
                }
            }

            // If none of the download attempts succeeded, then we should print out all the error
            // information.
            Console.WriteLine($"    Failed to download asset, errors shown below:");
            foreach (string error in errors)
            {
                Console.WriteLine($"      {error}");
            }

            return(downloadedAsset);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="client"></param>
        /// <param name="asset"></param>
        /// <param name="rootOutputDirectory"></param>
        /// <returns></returns>
        /// <remarks>
        ///     Layout:
        ///     {root dir}\shipping\assets - blobs
        ///     {root dir}\shipping\packages - packages
        ///     {root dir}\nonshipping\assets - blobs
        ///     {root dir}\nonshipping\packages - packages
        /// </remarks>
        private async Task <DownloadedAsset> DownloadAssetAsync(HttpClient client,
                                                                Build build,
                                                                Asset asset,
                                                                string rootOutputDirectory)
        {
            string assetNameAndVersion = GetAssetNameForLogging(asset);

            if (_options.IncludeNonShipping || !asset.NonShipping)
            {
                Console.WriteLine($"  Downloading asset {assetNameAndVersion}");
            }
            else
            {
                Console.WriteLine($"  Skipping non-shipping asset {assetNameAndVersion}");
                return(null);
            }

            DownloadedAsset downloadedAsset = new DownloadedAsset()
            {
                Successful = false,
                Asset      = asset
            };

            List <string> errors = new List <string>();

            List <AssetLocation> assetLocations = new List <AssetLocation>(asset.Locations);

            if (assetLocations.Count == 0)
            {
                // If there is no location information and the user wants workarounds, add a bunch
                // of feeds.
                if (!_options.NoWorkarounds)
                {
                    if (asset.Name.Contains("/"))
                    {
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetcli.blob.core.windows.net/dotnet/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetclichecksums.blob.core.windows.net/dotnet/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.Container, "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json"));
                    }
                    else
                    {
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-aspnetcore-tooling/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-entityframeworkcore/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/aspnet-extensions/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-core/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-coreclr/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-sdk/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-toolset/index.json"));
                        assetLocations.Add(new AssetLocation(0, LocationType.NugetFeed, "https://dotnetfeed.blob.core.windows.net/dotnet-windowsdesktop/index.json"));
                    }
                }
                else
                {
                    errors.Add($"Asset '{assetNameAndVersion}' has no known location information.");
                }
            }
            else
            {
                if (_options.LatestLocation)
                {
                    downloadedAsset = await DownloadAssetFromLatestLocation(client, build, asset, assetLocations, rootOutputDirectory, errors);
                }
                else
                {
                    downloadedAsset = await DownloadAssetFromAnyLocationAsync(client, build, asset, assetLocations, rootOutputDirectory, errors);
                }

                if (downloadedAsset.Successful)
                {
                    return(downloadedAsset);
                }
            }

            // If none of the download attempts succeeded, then we should print out all the error
            // information.
            Console.WriteLine($"    Failed to download asset, errors shown below:");
            foreach (string error in errors)
            {
                Console.WriteLine($"      {error}");
            }

            return(downloadedAsset);
        }