text:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

using TABS_UserControls.resources.code.BAL;
using System.Text;

namespace TABS_UserControls.usercontrols
{
    public partial class bugForm : System.Web.UI.UserControl
    {
        protected void Page_Load(object sender, EventArgs e)
        {

        }

        protected void btnSubmit_Click(object sender, EventArgs e)
        {
            try
            {
                InfrastructureClass infrastructure = new InfrastructureClass();

                string from = System.Configuration.ConfigurationManager.AppSettings["fromEmailAddress"].ToString();

                List<string> to = new List<string>();
                to.Add("tabs@tabs.org");

                StringBuilder body = new StringBuilder();
                body.Append("Browser name: ").Append(tbBrowserName.Text).Append("\r\n");
                body.Append("Browser version: ").Append(tbBrowserVersion.Text).Append("\r\n");
                body.Append("Operating system: ").Append(tbOperatingSystem.Text).Append("\r\n");
                body.Append("Description: ").Append(tbDescription.Text).Append("\r\n");
                body.Append("Name: ").Append(tbName.Text).Append("\r\n");
                body.Append("Email Address: ").Append(tbEmailAddress.Text).Append("\r\n");
                body.Append("Expected Results: ").Append(tbExpectedResults.Text).Append("\r\n");
                body.Append("Page URL: ").Append(tbPageURL.Text).Append("\r\n");

                infrastructure.SendEmail(from, to, new List<string>(), new List<string>(), "TABS Bug Reported", body.ToString(), false);

                ltlErrorMsg.Text = "Thank you, your message has been sent.";
            }
            catch (System.Exception ex)
            {
                ltlErrorMsg.Text = ex.Message;
            }
        }
    }
}


text:
@implementation HNContentView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        // Initialization code
    }
    return self;
}

- (void)awakeFromNib{
    [super awakeFromNib];
    self.backgroundColor = [UIColor cloudsColor];

}

@end

redpajama_set_name: RedPajamaGithub


text:
require 'protobuf/generators/file_generator'

module Protobuf
  class CodeGenerator

    CodeGeneratorFatalError = Class.new(RuntimeError)

    def self.fatal(message)
      fail CodeGeneratorFatalError, message
    end

    def self.print_tag_warning_suppress
      STDERR.puts "Suppress tag warning output with PB_NO_TAG_WARNINGS=1."
      def self.print_tag_warning_suppress; end
    end

    def self.warn(message)
      STDERR.puts("[WARN] #{message}")
    end

    private

    attr_accessor :request

    public

    def initialize(request_bytes)
      self.request = ::Google::Protobuf::Compiler::CodeGeneratorRequest.decode(request_bytes)
    end

    def generate_file(file_descriptor)
      ::Protobuf::Generators::FileGenerator.new(file_descriptor).generate_output_file
    end

    def response_bytes
      generated_files = request.proto_file.map do |file_descriptor|
        generate_file(file_descriptor)
      end

      ::Google::Protobuf::Compiler::CodeGeneratorResponse.encode(:file => generated_files)
    end

  end
end


text:
require "joycity_mallcoo/version"
require 'unirest'
require 'digest/md5'

module JoycityMallcoo
  # Your code goes here...
  class Info
    # test url
    URL = 'http://openapi.mallcoo.cn/rongyi/'

    def initialize appkey, secret, mall_id
      @appkey, @secret, @mall_id = appkey, secret, mall_id
    end

    def get_sig params_str
      Digest::MD5.hexdigest("#{params_str}&#{@secret}").upcase
    end

    def request action, params={}
      params_arr = []
      params.merge!(appkey: @appkey, ts: Time.now.to_i, mallID: @mall_id)
      params.each_pair{|k, v| params_arr << "#{k}=#{v}" }
      params_str = params_arr.sort.join("&")
      sig = get_sig params_str

      JSON.parse(Unirest.post("#{URL}#{action}/?#{params_str}&sig=#{sig}").raw_body)
    end

    # 获取容易网首页信息
    def get_index_info
      request "GETIndexInfo"
    end

    # 获取获取商场活动
    def get_activities
      request "GetActivities"
    end

    # 按商户ID返回商户详细信息
    def get_brand_by_id brand_id
      request "GetBrandById", brandID: brand_id
    end

    # 按商户类型ID返回商户
    def get_brand_by_type_id brand_type_id
      request "GetBrandByTypeID", brandTypeID: brand_type_id
    end

    # 根据拼音首字母搜索
    def get_brand_by_type_letter letter
      request "GetBrandByTypeLetter", letter: letter
    end

    # 获取有优惠的品牌优惠信
    def get_brand_have_preferential
      request "GetBrandHavePreferential"
    end

    # 获取品牌类型
    def get_brand_type
      request "GetBrandType"
    end

    # 查询会员积分记录
    def get_member_bonus_record card_no, page, page_size
      request "GetMemberBonusLedgerRowCount", cardNo: card_no, pageIndex: page, pageSize: page_size
    end

    # 获取礼品列表
    def get_all_gift
      request "GetAllGift"
    end

    # 用户能兑换的礼品
    def get_gift_by_user card_no
      request "GetGiftbyUser", cardNo: card_no
    end

    # 兑换礼品
    # 此处礼品ID参数的名称与文档中不一致,正确的参数名为 giftID,而非 giftId --2014-07-28 注
    def points_exchange_gift card_no, gift_id
      request "PointsExchangeGift", cardNo: card_no, giftID: gift_id
    end

  end
end


text:
package com.github.moronicgeek.swagger.cloud.server.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * Created by muhammedpatel on 2016/08/01.
 */
@ConfigurationProperties("spring.swagger.cloud")
public class SwaggerCloudProperties {

    private String server = "localhost";
    private int port = 8080;

    public String getServer() {
        return server;
    }

    public void setServer(String server) {
        this.server = server;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }
}


text:
#ifndef HALIDE_BOUNDS_H
#define HALIDE_BOUNDS_H

#include "IR.h"
#include "IRVisitor.h"
#include "Scope.h"
#include <utility>
#include <vector>

/** \file
 * Methods for computing the upper and lower bounds of an expression,
 * and the regions of a function read or written by a statement.
 */

namespace Halide {
namespace Internal {

struct Interval {
    Expr min, max;
    Interval() {}
    Interval(Expr min, Expr max) : min(min), max(max) {}
};

/** Given an expression in some variables, and a map from those
 * variables to their bounds (in the form of (minimum possible value,
 * maximum possible value)), compute two expressions that give the
 * minimum possible value and the maximum possible value of this
 * expression. Max or min may be undefined expressions if the value is
 * not bounded above or below.
 *
 * This is for tasks such as deducing the region of a buffer
 * loaded by a chunk of code.
 */
Interval bounds_of_expr_in_scope(Expr expr, const Scope<Interval> &scope);

/** Compute rectangular domains large enough to cover all the 'Call's
 * to each function that occurs within a given statement. This is
 * useful for figuring out what regions of things to evaluate. */
std::map<std::string, Region> regions_called(Stmt s);

/** Compute rectangular domains large enough to cover all the
 * 'Provide's to each function that occur within a given
 * statement. This is useful for figuring out what region of a
 * function a scattering reduction (e.g. a histogram) might touch. */
std::map<std::string, Region> regions_provided(Stmt s);

/** Compute rectangular domains large enough to cover all Calls and
 * Provides to each function that occurs within a given statement */
std::map<std::string, Region> regions_touched(Stmt s);

/** Compute a rectangular domain large enough to cover all Calls and
 * Provides to a given function */
Region region_touched(Stmt s, const std::string &func);

/** Compute a rectangular domain large enough to cover all Provides to
 * a given function */
Region region_provided(Stmt s, const std::string &func);

/** Compute a rectangular domain large enough to cover all Calls
 * to a given function */
Region region_called(Stmt s, const std::string &func);

/** Compute the smallest bounding box that contains two regions */
Region region_union(const Region &, const Region &);

void bounds_test();

}
}

#endif


text:
from distutils.errors import DistutilsOptionError
from unittest import TestLoader
import sys

from pkg_resources import (resource_listdir, resource_exists, normalize_path,
                           working_set, _namespace_packages,
                           add_activation_listener, require, EntryPoint)
from setuptools import Command
from setuptools.compat import PY3
from setuptools.py31compat import unittest_main

class ScanningLoader(TestLoader):
    def loadTestsFromModule(self, module, pattern=None):
        """Return a suite of all tests cases contained in the given module

        If the module is a package, load tests from all the modules in it.
        If the module has an ``additional_tests`` function, call it and add
        the return value to the tests.
        """
        tests = []
        tests.append(TestLoader.loadTestsFromModule(self, module))

        if hasattr(module, "additional_tests"):
            tests.append(module.additional_tests())

        if hasattr(module, '__path__'):
            for file in resource_listdir(module.__name__, ''):
                if file.endswith('.py') and file != '__init__.py':
                    submodule = module.__name__ + '.' + file[:-3]
                else:
                    if resource_exists(module.__name__, file + '/__init__.py'):
                        submodule = module.__name__ + '.' + file
                    else:
                        continue
                tests.append(self.loadTestsFromName(submodule))

        if len(tests) != 1:
            return self.suiteClass(tests)
        else:
            return tests[0]  # don't create a nested suite for only one return

class test(Command):
    """Command to run unit tests after in-place build"""

    description = "run unit tests after in-place build"

    user_options = [
        ('test-module=', 'm', "Run 'test_suite' in specified module"),
        ('test-suite=', 's',
         "Test suite to run (e.g. 'some_module.test_suite')"),
        ('test-runner=', 'r', "Test runner to use"),
    ]

    def initialize_options(self):
        self.test_suite = None
        self.test_module = None
        self.test_loader = None
        self.test_runner = None

    def finalize_options(self):

        if self.test_suite and self.test_module:
            msg = "You may specify a module or a suite, but not both"
            raise DistutilsOptionError(msg)

        if self.test_suite is None:
            if self.test_module is None:
                self.test_suite = self.distribution.test_suite
            else:
                self.test_suite = self.test_module + ".test_suite"

        if self.test_loader is None:
            self.test_loader = getattr(self.distribution, 'test_loader', None)
        if self.test_loader is None:
            self.test_loader = "setuptools.command.test:ScanningLoader"
        if self.test_runner is None:
            self.test_runner = getattr(self.distribution, 'test_runner', None)

    @property
    def test_args(self):
        return list(self._test_args())

    def _test_args(self):
        if self.verbose:
            yield '--verbose'
        if self.test_suite:
            yield self.test_suite

    def with_project_on_sys_path(self, func):
        with_2to3 = PY3 and getattr(self.distribution, 'use_2to3', False)

        if with_2to3:
            # If we run 2to3 we can not do this inplace:

            # Ensure metadata is up-to-date
            self.reinitialize_command('build_py', inplace=0)
            self.run_command('build_py')
            bpy_cmd = self.get_finalized_command("build_py")
            build_path = normalize_path(bpy_cmd.build_lib)

            # Build extensions
            self.reinitialize_command('egg_info', egg_base=build_path)
            self.run_command('egg_info')

            self.reinitialize_command('build_ext', inplace=0)
            self.run_command('build_ext')
        else:
            # Without 2to3 inplace works fine:
            self.run_command('egg_info')

            # Build extensions in-place
            self.reinitialize_command('build_ext', inplace=1)
            self.run_command('build_ext')

        ei_cmd = self.get_finalized_command("egg_info")

        old_path = sys.path[:]
        old_modules = sys.modules.copy()

        try:
            sys.path.insert(0, normalize_path(ei_cmd.egg_base))
            working_set.__init__()
            add_activation_listener(lambda dist: dist.activate())
            require('%s==%s' % (ei_cmd.egg_name, ei_cmd.egg_version))
            func()
        finally:
            sys.path[:] = old_path
            sys.modules.clear()
            sys.modules.update(old_modules)
            working_set.__init__()

    def run(self):
        if self.distribution.install_requires:
            self.distribution.fetch_build_eggs(
                self.distribution.install_requires)
        if self.distribution.tests_require:
            self.distribution.fetch_build_eggs(self.distribution.tests_require)

        cmd = ' '.join(self._argv)
        if self.dry_run:
            self.announce('skipping "%s" (dry run)' % cmd)
        else:
            self.announce('running "%s"' % cmd)
            self.with_project_on_sys_path(self.run_tests)

    def run_tests(self):
        # Purge modules under test from sys.modules. The test loader will
        # re-import them from the build location. Required when 2to3 is used
        # with namespace packages.
        if PY3 and getattr(self.distribution, 'use_2to3', False):
            module = self.test_suite.split('.')[0]
            if module in _namespace_packages:
                del_modules = []
                if module in sys.modules:
                    del_modules.append(module)
                module += '.'
                for name in sys.modules:
                    if name.startswith(module):
                        del_modules.append(name)
                list(map(sys.modules.__delitem__, del_modules))

        unittest_main(
            None, None, self._argv,
            testLoader=self._resolve_as_ep(self.test_loader),
            testRunner=self._resolve_as_ep(self.test_runner),
        )

    @property
    def _argv(self):
        return ['unittest'] + self.test_args

    @staticmethod
    def _resolve_as_ep(val):
        """
        Load the indicated attribute value, called, as a as if it were
        specified as an entry point.
        """
        if val is None:
            return
        parsed = EntryPoint.parse("x=" + val)
        return parsed.resolve()()


text:
<?php defined('SYSPATH') or die('No direct script access.'); ?>

2011-07-06 02:39:40 --- ERROR: ErrorException [ 8 ]: Undefined variable: id ~ APPPATH/classes/controller/admin/article.php [ 233 ]
2011-07-06 02:55:11 --- ERROR: HTTP_Exception_404 [ 404 ]: Unable to find a route to match the URI: src/js/init.php ~ SYSPATH/classes/kohana/request.php [ 760 ]


text:
<?php

class Messages_model extends MY_Model{

    function __construct(){
        parent::__construct();
        $this->teamid = $this->session->userdata('team_id');
        $this->current_year = $this->session->userdata('current_year');
        $this->current_week = $this->session->userdata('current_week');
        $this->ownerid = $this->session->userdata('owner_id');
    }

    function get_league_teams_data()
    {
    	return $this->db->select('id, team_name')->from('team')
    		->where('league_id',$this->leagueid)
    		->where('active',1)
    		->where_not('id',$this->teamid)
            ->order_by('team_name','asc')
    		->get()->result();
    }

    function get_league_owners_data()
    {
    	return $this->db->select('owner.id as owner_id, team.id as team_id, owner.first_name, owner.last_name')
    		->from('team')->join('owner', 'owner.id = team.owner_id')
    		->where('team.active',1)->where('team.league_id',$this->leagueid)
            ->order_by('owner.last_name','asc')
    		->get()->result();
    }

    function insert_new_message($to, $subject, $body)
    {
        $message_date = date("Y-m-d H:i:s");
        // One copy for sender, sent items folder, read
        $this->db->insert('message', array('team_id' => $this->teamid,
                                          'to_team_id' => $to,
                                          'from_team_id' => $this->teamid,
                                          'subject' => $subject,
                                          'body' => $body,
                                          'read' => 1,
                                          'message_date' => $message_date,
                                          'folder_id' => 1));

        // One copy for recipient, inbox, unread
        $this->db->insert('message', array('team_id' => $to,
                                          'to_team_id' => $to,
                                          'from_team_id' => $this->teamid,
                                          'subject' => $subject,
                                          'body' => $body,
                                          'read' => 0,
                                          'message_date' => $message_date,
                                          'folder_id' => 0));

        $body = "League: ".$this->session->userdata('league_name').
                "\nMessage From: ".$this->session->userdata('first_name')." ".$this->session->userdata('last_name').
                " (".$this->session->userdata('team_name').")\n-------------------\n\n".$body;
        $this->email_new_message($to,$subject,$body);

    }

    function email_new_message($to, $subject, $body)
    {
        $recipient = $this->db->select('email')->from('team')->join('owner','owner.id = team.owner_id')
            ->join('user_accounts','user_accounts.id = owner.user_accounts_id')
            ->where('team.id',$to)->get()->row()->email;

        $this->config->load('fflproject');
        $this->load->library('email');
        $this->email->from($this->config->item('fflp_email_reply_to'), $this->config->item('fflp_email_site_title'));
        $this->email->to($recipient);
        $this->email->subject($subject);
        $body = prepare_email_body($body);
        $this->email->message($body);
        $this->email->send();
    }

    function get_messages_from_folder($folder_id)
    {
        return $this->db->select('from_team_id, subject, UNIX_TIMESTAMP(message_date) as unix_date')
            ->select('message.id, read, team.team_name as from_team_name, owner.first_name, owner.last_name')
            ->from('message')->join('team','team.id = message.from_team_id')
            ->join('owner', 'owner.id = team.owner_id')
            ->where('team_id',$this->teamid)->where('folder_id',$folder_id)
            ->order_by('unix_date','desc')
            ->get()->result();
    }

    function get_message($id)
    {
        $data = array('read' => 1);
        $this->db->where('id',$id);
        $this->db->update('message',$data);
        return $this->db->select('from_team_id, subject, UNIX_TIMESTAMP(message_date) as unix_date, body')
            ->select('message.id, read, team.team_name as from_team_name, owner.first_name, owner.last_name')
            ->from('message')->join('team','team.id = message.from_team_id')
            ->join('owner', 'owner.id = team.owner_id')
            ->where('message.id',$id)->get()->row();
    }

    function trash_message($id)
    {
        $this->db->where('id',$id)->update('message', array('folder_id' => 2));
    }

    function delete_message($id,$forever=false)
    {
        $message = $this->db->select('folder_id')->from('message')->where('id',$id)->get()->row();
        if ($message->folder_id == 2 || $forever)
        {
            $this->db->delete('message', array('id' => $id));
            return "Message deleted forever.";
        }
        else
        {
            $this->trash_message($id);
            return "Message moved to trash.";
        }
    }
}


text:
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <description>Common static functions</description>
    <isExposed>false</isExposed>
    <masterLabel>Util Common</masterLabel>
</LightningComponentBundle>


text:
package net.sf.mmm.client.ui.api.feature;

import net.sf.mmm.client.ui.api.attribute.AttributeWriteValueAdvanced;
import net.sf.mmm.client.ui.api.handler.event.UiHandlerEventValueChange;

/**
 * This is the interface for the {@link UiFeature features} of an object that has a {@link #getValue() value}
 * that can be {@link net.sf.mmm.util.lang.api.attribute.AttributeWriteValue#setValue(Object) changed
 * programmatic} as well as by the user (e.g. by typing in a text input field). Additionally, it allows to
 * {@link #addChangeHandler(UiHandlerEventValueChange) add} and
 * {@link #removeChangeHandler(UiHandlerEventValueChange) remove} instances of
 * {@link UiHandlerEventValueChange}.
 *
 * @param <VALUE> is the generic type of the {@link #getValue() value}.
 *
 * @author Joerg Hohwiller (hohwille at users.sourceforge.net)
 * @since 1.0.0
 */
public interface UiFeatureValue<VALUE> extends UiFeature, AttributeWriteValueAdvanced<VALUE> {

  /**
   * This method adds the given {@link UiHandlerEventValueChange} to this object.
   *
   * @param handler is the {@link UiHandlerEventValueChange} to add.
   */
  void addChangeHandler(UiHandlerEventValueChange<VALUE> handler);

  /**
   * This method removes the given {@link UiHandlerEventValueChange} from this object.
   *
   * @param handler is the {@link UiHandlerEventValueChange} to remove.
   * @return true if the handler has been removed successfully, false
   *         if it was NOT {@link #addChangeHandler(UiHandlerEventValueChange) registered} and nothing has
   *         changed.
   */
  boolean removeChangeHandler(UiHandlerEventValueChange<VALUE> handler);

}


text:
package org.traccar.protocol;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.traccar.BaseProtocolEncoder;
import org.traccar.helper.Checksum;
import org.traccar.helper.DataConverter;
import org.traccar.model.Command;
import org.traccar.Protocol;

import java.nio.charset.StandardCharsets;

public class RuptelaProtocolEncoder extends BaseProtocolEncoder {

    public RuptelaProtocolEncoder(Protocol protocol) {
        super(protocol);
    }

    public static ByteBuf encodeContent(int type, ByteBuf content) {

        ByteBuf buf = Unpooled.buffer();

        buf.writeShort(1 + content.readableBytes());
        buf.writeByte(100 + type);
        buf.writeBytes(content);
        buf.writeShort(Checksum.crc16(Checksum.CRC16_KERMIT, buf.nioBuffer(2, buf.writerIndex() - 2)));

        return buf;
    }

    @Override
    protected Object encodeCommand(Command command) {

        ByteBuf content = Unpooled.buffer();

        switch (command.getType()) {
            case Command.TYPE_CUSTOM:
                String data = command.getString(Command.KEY_DATA);
                if (data.matches("(\\p{XDigit}{2})+")) {
                    content.writeBytes(DataConverter.parseHex(data));
                    return content;
                } else {
                    content.writeBytes(data.getBytes(StandardCharsets.US_ASCII));
                    return encodeContent(RuptelaProtocolDecoder.MSG_SMS_VIA_GPRS, content);
                }
            case Command.TYPE_REQUEST_PHOTO:
                content.writeByte(1); // sub-command
                content.writeByte(0); // source
                content.writeInt(0); // start timestamp
                content.writeInt(Integer.MAX_VALUE); // end timestamp
                return encodeContent(RuptelaProtocolDecoder.MSG_FILES, content);
            case Command.TYPE_CONFIGURATION:
                content.writeBytes((command.getString(Command.KEY_DATA) + "\r\n").getBytes(StandardCharsets.US_ASCII));
                return encodeContent(RuptelaProtocolDecoder.MSG_DEVICE_CONFIGURATION, content);
            case Command.TYPE_GET_VERSION:
                return encodeContent(RuptelaProtocolDecoder.MSG_DEVICE_VERSION, content);
            case Command.TYPE_FIRMWARE_UPDATE:
                content.writeBytes("|FU_STRT*\r\n".getBytes(StandardCharsets.US_ASCII));
                return encodeContent(RuptelaProtocolDecoder.MSG_FIRMWARE_UPDATE, content);
            case Command.TYPE_OUTPUT_CONTROL:
                content.writeInt(command.getInteger(Command.KEY_INDEX));
                content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA)));
                return encodeContent(RuptelaProtocolDecoder.MSG_SET_IO, content);
            case Command.TYPE_SET_CONNECTION:
                String c = command.getString(Command.KEY_SERVER) + "," + command.getInteger(Command.KEY_PORT) + ",TCP";
                content.writeBytes(c.getBytes(StandardCharsets.US_ASCII));
                return encodeContent(RuptelaProtocolDecoder.MSG_SET_CONNECTION, content);
            case Command.TYPE_SET_ODOMETER:
                content.writeInt(Integer.parseInt(command.getString(Command.KEY_DATA)));
                return encodeContent(RuptelaProtocolDecoder.MSG_SET_ODOMETER, content);
            default:
                return null;
        }
    }

}


text:
$ErrorActionPreference = "Stop"

$Separator = "--------------------------------------------------------------------------------------------------------------------------------"
$DefaultDownloadFolder = "C:\Downloads"

[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

#####################################################################################################
# Start-Setup
#####################################################################################################

<#
    .SYNOPSIS
        Sets up the context for the build script to work.
    .DESCRIPTION
        Prints out disk size information and sets up the downloaded content folder.
#>
function Start-Setup
{
    Write-Host $Separator

    Trace-Message "Starting installation"

    Trace-Message "Checking disk space"
    gwmi win32_logicaldisk | Format-Table DeviceId, MediaType, {$_.Size /1GB}, {$_.FreeSpace /1GB}

    Trace-Message "Creating download location C:\Downloads"
    New-Item -Path $DefaultDownloadFolder -ItemType Container -ErrorAction SilentlyContinue
}

#####################################################################################################
# Stop-Setup
#####################################################################################################

<#
    .SYNOPSIS
        Shuts down the build script.
    .DESCRIPTION
        Deletes the downloaded content folder. Cleans the contents of the TEMP folder. Prints
        out a list of the installed software on the image by querying WMIC.
    .PARAMETER PreserveDownloads
        Preserves the downloaded content folder.
    .PARAMETER PreserveTemp
        Preserves the temp folder contents.
#>
function Stop-Setup
{
    param
    (
        [Parameter(Mandatory=$false)]
        [switch]$PreserveDownloads,

        [Parameter(Mandatory=$false)]
        [switch]$PreserveTemp
    )

    Write-Host $Separator

    if (-not $PreserveDownloads)
    {
        Trace-Message "Deleting download location C:\Downloads"
        Remove-Item -Path "C:\Downloads" -Recurse -ErrorAction SilentlyContinue
    }

    if (-not $PreserveTemp)
    {
        Reset-TempFolders
    }

    Trace-Message "Checking disk space"
    gwmi win32_logicaldisk | Format-Table DeviceId, MediaType, {$_.Size /1GB}, {$_.FreeSpace /1GB}

    Trace-Message "Listing installed 32-bit software"
    Get-ItemProperty HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate | Sort-Object DisplayName,DisplayVersion,Publisher,InstallDate

    Trace-Message "Listing installed 64-bit software"
    Get-ItemProperty HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\* | Select-Object DisplayName,DisplayVersion,Publisher,InstallDate | Sort-Object DisplayName,DisplayVersion,Publisher,InstallDate

    Trace-Message "Finished installation."
    Write-Host $Separator
}

#####################################################################################################
# Get-File
#####################################################################################################

<#
    .SYNOPSIS
        Downloads a file from a URL to the downloaded contents folder.
    .DESCRIPTION
        Fetches the contents of a file from a URL to the downloaded contents folder (C:\Downloads).
        If a specific FilePath is specified, then skips the cache folder and downloads to the
        specified path.
    .PARAMETER Url
        The URL of the content to fetch.
    .PARAMETER FileName
        The name of the file to write the fetched content to.
    .OUTPUTS
        The full path to the downloaded file.
#>
function Get-File
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Url,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$FileName
    )

    Write-Host $Separator

    $file = [System.IO.Path]::Combine("C:\Downloads", $FileName)

    Trace-Message "Downloading from $Url to file $File"
    Invoke-WebRequest -Uri $Url -UseBasicParsing -OutFile $file -UserAgent "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)"

    Trace-Message "Finished download"
    Write-Host $Separator

    return $file
}

#####################################################################################################
# Get-UniversalPackage
#####################################################################################################

<#
    .SYNOPSIS
        Downloads a universal package from Azure DevOps.
    .DESCRIPTION
        Downloads an universal package from an Azure DevOps package feed.
        Universal packages are used to distributed arbitrary blob content
        and is used by CDPX to store tools that need to be installed into
        build containers.
    .PARAMETER Name
        The name of the package to download.
    .PARAMETER Version
        The version of the package to download.
    .PARAMETER Feed
        The name of the package feed to download from.
        The default is the CX_External_Software feed in the OneBranch account.
    .PARAMETER Account
        The Azure DevOps account or organization name that the feed is in.
        The default is the OneBranch account.
    .PARAMETER Path
        The path to download the package into. THis parameter is only used
        if the switch IgnoreDefaultPath is not specified.
    .PARAMETER Token
        A PAT token to use to perform the download operation. Defaults to
        using the environment variable TEMP_CDP_DEFAULT_CLIENT_PACKAGE_PAT
        if no token is specified.
    .OUTPUTS
        The path to the package folder. A new folder named after the
        package will be created in the standard download location of
        C:\Downlaods unless the Path parameter is provided. If the Path
        parameter is provided, then that path is returned instead.
#>
function Get-UniversalPackage
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Version,

        [Parameter(Mandatory=$false)]
        [string]$Feed="CX_External_Software@Local",

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Account="onebranch",

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Path="C:\Downloads",

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Token = $Env:TEMP_CDP_DEFAULT_CLIENT_PACKAGE_PAT,

        [switch]$IgnoreDefaultPath,

        [switch]$EnableDiagnosticLogs
    )

    Write-Host $Separator

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    if (-not $IgnoreDefaultPath)
    {
        $p = Join-Path -Path "C:\Downloads" -ChildPath $Name
        Trace-Message "IgnoreDefaultPath switch was NOT set. Using path $p as download path."
        $Path = $p
    }
    else
    {
        Trace-Message "IgnoreDefaultPath switch was set. Using path $Path to download package to."
    }

    $o = -join("https://dev.azure.com/", $Account, "/")

    Trace-Message "Invoking Azure DevOps CLI to download package $Name version $Version to $Path from $o and feed $Feed ..."

    $Arguments = @("artifacts", "universal", "download", "--name", $Name, "--version", $Version, "--feed", $Feed, "--organization", $o, "--path", "$Path")

    if ($EnableDiagnosticLogs)
    {
         $Arguments += "--debug"
    }

    $Env:AZURE_DEVOPS_EXT_PAT=$Token
    Write-Host $Arguments
    $exCode = Start-ExternalProcess -Path "C:\Program Files (x86)\Microsoft SDKs\Azure\CLI2\wbin\az.cmd" -Arguments $Arguments

    if ($exCode -ne 0)
    {
        throw "CDPXERROR: Failed to download package $Name version $Version to $Path from $o and feed $Feed. Azure DevOps CLI exited with code $exCode"
    }

    Trace-Message "Finished downloading universal package"
    Write-Host $Separator

    return $Path
}

#####################################################################################################
# Login-AzureDevOps
#####################################################################################################

<#
    .SYNOPSIS
        Performs a login to Azure DevOps.
    .DESCRIPTION
        Uses the temporary PAT token to login to Azure DevOps so that universal
        packages can be downloaded.
    .PARAMETER Account
        The Azure DevOps account or organization name to login to. The default is the
        OneBranch account.
    .PARAMETER Token
        A PAT token to use to perform the login operation. Defaults to
        using the environment variable TEMP_CDP_DEFAULT_CLIENT_PACKAGE_PAT
        if no token is specified.
#>
function Login-AzureDevOps
{
    param
    (
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Account="onebranch",

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Token = $Env:TEMP_CDP_DEFAULT_CLIENT_PACKAGE_PAT,

        [switch]$EnableDiagnosticLogs
    )

    Write-Host $Separator

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $o = -join("https://dev.azure.com/", $Account, "/")

    Write-Host "Invoking Azure DevOps CLI to login to account $o"

    $args = @($o, $Token)

    if ($EnableDiagnosticLogs)
    {
        $args += ("--debug")
    }

    &"$PSScriptRoot\ado-login.cmd" $args

    if ($LASTEXITCODE -ne 0)
    {
        throw "CDPXERROR: Failed to login to account $o with provided PAT token. Azure DevOps CLI exited with code $LASTEXITCODE"
    }

    Trace-Message "Finished login to Azure DevOps"
    Write-Host $Separator
}

#####################################################################################################
# Add-EnvironmentVariable
#####################################################################################################

<#
    .SYNOPSIS
        Defines a new or redefines an existing environment variable.
    .DESCRIPTION
        There are many ways to set environment variables. However, the default mechanisms do not
        work when the change has to be persisted. This implementation writes the change into
        the registry, invokes the .NET SetEnvironmentVariable method with Machine scope and then
        invokes setx /m to force persistence of the change.
    .PARAMETER Name
        The name of the environment variable.
    .PARAMETER Value
        The value of the environment variable.
    .NOTES
        This does NOT work with PATH.
#>
function Add-EnvironmentVariable
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [string]$Value
    )

    Write-Host $Separator

    Trace-Message "Setting environment variable $name := $value"

    Set-Item -Path Env:$Name -Value $Value
    New-Item -Path "HKLM:\System\CurrentControlSet\Control\Session Manager\Environment" -ItemType String -Force -Name $Name -Value $Value

    [System.Environment]::SetEnvironmentVariable($Name, $Value, [EnvironmentVariableTarget]::Machine)

    &setx.exe /m $Name $Value

    Write-Host $Separator
}

#####################################################################################################
#  Update-Path
#####################################################################################################

<#
    .SYNOPSIS
        Redefines the PATH.
    .DESCRIPTION
        There are many ways to set environment variables. However, the default mechanisms do not
        work when the change has to be persisted. This implementation writes the change into
        the registry, invokes the .NET SetEnvironmentVariable method with Machine scope and then
        invokes setx /m to force persistence of the change.
    .PARAMETER PathNodes
        An array of changes to the PATH. These values are appended to the existing value of PATH at the end.
    .NOTES
        This does NOT seem to work at all in Windows containers. Yet to be tested on RS5, but
        definitely did not work in RS1 through RS4.
#>
function Update-Path
{
    param
    (
        [Parameter(Mandatory=$true)]
        [string[]]$PathNodes
    )

    Write-Host $Separator

    $NodeToAppend=$null

    $path = $env:Path

    Trace-Message "Current value of PATH := $path"
    Trace-Message "Appending $Update to PATH"

    if (!$path.endswith(";"))
    {
      $path = $path + ";"
    }

    foreach ($PathNode in $PathNodes)
    {
       if (!$PathNode.endswith(";"))
       {
       $PathNode = $PathNode + ";"
       }
    $NodesToAppend += $PathNode
    }
# add the new nodes
    $path = $path + $NodesToAppend

#prettify it because there is some cruft from base images and or path typos i.e. foo;;
    $path = $path -replace ";+",";"

#pull these in a hack until remove nodes is implemented
    $path = $path.Replace("C:\Program Files\NuGet;","")
    $path = $path.Replace("C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\MSBuild\Current\Bin;","")
    $path = $path.Replace("C:\Program Files (x86)\Microsoft Visual Studio\2019\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;","")

#and set it
    Trace-Message "Setting PATH to $path"
    [System.Environment]::SetEnvironmentVariable("PATH", $path, [EnvironmentVariableTarget]::Machine)

    Write-Host $Separator
}

#####################################################################################################
# Add-WindowsFeature
#####################################################################################################

<#
    .SYNOPSIS
        Simple wrapper around the Install-WindowsFeature cmdlet.
    .DESCRIPTION
        A simple wrapper around the Install-WindowsFeature cmdlet that writes log lines and
        data to help trace what happened.
    .PARAMETER Name
        The name of the feature to install.

    .PARAMETER SourceString
        The full -Source parameter with location to pass into install-WindowsFeature
#>
function Add-WindowsFeature
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$SourceLocation=$null

    )

    Write-Host $Separator

    Trace-Message "Installing Windows feature $Name"

    if ($SourceLocation)
    {
      Install-WindowsFeature -Name $Name -Source $SourceLocation -IncludeAllSubFeature -IncludeManagementTools -Restart:$false -Confirm:$false
    }
    else
    {
      Install-WindowsFeature -Name $Name -IncludeAllSubFeature -IncludeManagementTools -Restart:$false -Confirm:$false
    }

    Trace-Message "Finished installing Windows feature $Name"

    Write-Host $Separator
}

#####################################################################################################
# Remove-WindowsFeature
#####################################################################################################

<#
    .SYNOPSIS
        Simple wrapper around the Uninstall-WindowsFeature cmdlet.
    .DESCRIPTION
        A simple wrapper around the Uninstall-WindowsFeature cmdlet that writes log lines and
        data to help trace what happened.
    .PARAMETER Name
        The name of the feature to uninstall.
#>
function Remove-WindowsFeature
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )

    Write-Host $Separator

    Trace-Message "Removing Windows feature $Name"

    Uninstall-WindowsFeature -Name $Name -IncludeManagementTools -Restart:$false -Confirm:$false

    Trace-Message "Finished removing Windows feature $Name"

    Write-Host $Separator
}

#####################################################################################################
# Install-FromMSI
#####################################################################################################

<#
    .SYNOPSIS
        Executes a Microsoft Installer package (MSI) in quiet mode.
    .DESCRIPTION
        Uses the msiexec tool with the appropriate arguments to execute the specified installer
        package in quiet non-interactive mode with full verbose logging enabled.
    .PARAMETER Path
        The full path to the installer package file.
    .PARAMETER Arguments
        The optioal arguments to pass to the MSI installer package.
    .PARAMETER IgnoreExitCodes
        An array of exit codes to ignore. By default 3010 is always ignored because that indicates
        a restart is required. Docker layers are an implied restart. In other scenarios such as
        image builds or local runs, a restart can be easily triggered by the invoking script or
        user.
    .PARAMETER IgnoreFailures
        Flag to force all failures (including actual failing exit codes) to be ignored. Notably
        1603 is a very common one that indicates that an actual error occurred.
#>
function Install-FromMSI
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments,

        [Parameter(Mandatory=$false)]
        [int[]]$IgnoreExitCodes,

        [switch]$IgnoreFailures
    )

    Write-Host $Separator

    if (-not (Test-Path $Path))
    {
        throw "CDPXERROR: Could not find the MSI installer package at $Path"
    }

    $fileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($Path)

    $log = [System.IO.Path]::Combine($env:TEMP, $fileNameOnly + ".log")

    $args = "/quiet /qn /norestart /lv! `"$log`" /i `"$Path`" $Arguments"

    Trace-Message "Installing from $Path"
    Trace-Message "Running msiexec.exe $args"

    $ex = Start-ExternalProcess -Path "msiexec.exe" -Arguments $args

    if ($ex -eq 3010)
    {
        Trace-Message "Install from $Path exited with code 3010. Ignoring since that is just indicating restart required."
        Write-Host $Separator
        return
    }
    elseif ($ex -ne 0)
    {
        foreach ($iex in $IgnoreExitCodes)
        {
            if ($ex -eq $iex)
            {
                Trace-Message "Install from $Path succeeded with exit code $ex"
                Write-Host $Separator
                return
            }
        }

        Trace-Error "Failed to install from $Path. Process exited with code $ex"

        if (-not $IgnoreFailures)
        {
            throw "Failed to install from $Path. Process exited with code $ex"
        }
    }
}

#####################################################################################################
# Install-FromEXE
#####################################################################################################

<#
    .SYNOPSIS
        Executes any arbitrary executable installer.
    .DESCRIPTION
        A simple wrapper function to kick off an executable installer and handle failures, logging etc.
    .PARAMETER Path
        The path to the installer package file.
    .PARAMETER Arguments
        The optioal arguments to pass to the installer package.
    .PARAMETER IgnoreExitCodes
        An array of exit codes to ignore. By default 3010 is always ignored because that indicates
        a restart is required. Docker layers are an implied restart. In other scenarios such as
        image builds or local runs, a restart can be easily triggered by the invoking script or
        user.
    .PARAMETER IgnoreFailures
        Flag to force all failures (including actual failing exit codes) to be ignored. Notably
        1603 is a very common one that indicates that an actual error occurred.
#>
function Install-FromEXE
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [int[]]$IgnoreExitCodes,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments,

        [switch]$IgnoreFailures
    )

    Write-Host $Separator

    Trace-Message "Running $Path"

    $ex = Start-ExternalProcess -Path $Path -Arguments $Arguments

    if ($ex -eq 3010)
    {
        Trace-Message "Install from $Path exited with code 3010. Ignoring since that is just indicating restart required."
        Write-Host $Separator
        return
    }
    elseif ($ex -ne 0)
    {
        foreach ($iex in $IgnoreExitCodes)
        {
            if ($ex -eq $iex)
            {
                Trace-Message "Install from $Path succeeded with exit code $ex"
                Write-Host $Separator
                return
            }
        }

        Trace-Error "Failed to install from $Path. Process exited with code $ex"

        if (-not $IgnoreFailures)
        {
            throw "Failed to install from $Path. Process exited with code $ex"
        }
    }
}

#####################################################################################################
# Install-FromInnoSetup
#####################################################################################################

<#
    .SYNOPSIS
        A shorthand function for running a Inno Setup installer package with the appropriate options.
    .DESCRIPTION
        Inno Setup installer packages can be run in silent mode with the options
        /VERYSILENT /NORESTART /CLOSEAPPLICATIONS /TYPE=full. In most cases, these options are the
        same for every Inno Setup installer. This function is hence a short hand for Inno Setup.
    .PARAMETER Path
        The path to the Inno Setup installer package file.
    .PARAMETER Arguments
        The optioal arguments to pass to the installer package.
    .PARAMETER IgnoreExitCodes
        An array of exit codes to ignore.
    .PARAMETER IgnoreFailures
        Flag to force all failures (including actual failing exit codes) to be ignored.

#>
function Install-FromInnoSetup
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [int[]]$IgnoreExitCodes,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments,

        [switch]$IgnoreFailures
    )

    $fileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($Path)
    $logName = $fileNameOnly + ".log"
    $logFile = Join-Path $Env:TEMP -ChildPath $logName

    $args = "/QUIET /SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /CLOSEAPPLICATIONS /NOICONS /TYPE=full /LOG `"$logFile`" "
    $args += $Arguments

    Install-FromEXE -Path $Path -Arguments $args -IgnoreExitCodes $IgnoreExitCodes -IgnoreFailures:$IgnoreFailures
}

#####################################################################################################
# Install-FromDevToolsInstaller
#####################################################################################################

<#
    .SYNOPSIS
        A shorthand function for running a DevDiv Tools installer package with the appropriate options.
    .DESCRIPTION
        DevDiv Tools installer packages can be run in silent mode with the options
        /quiet /install /norestart. In most cases, these options are the
        same for every DevDiv Tools installer. This function is hence a short hand for DevDiv Tools
        installer packages.
    .PARAMETER Path
        The path to the DevDiv Tools installer package file.
    .PARAMETER Arguments
        The optional arguments to pass to the installer package.
    .PARAMETER IgnoreExitCodes
        An array of exit codes to ignore. 3010 is added by default by this function.
    .PARAMETER IgnoreFailures
        Flag to force all failures (including actual failing exit codes) to be ignored.

#>
function Install-FromDevDivToolsInstaller
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [int[]]$IgnoreExitCodes,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments,

        [switch]$IgnoreFailures
    )

    $fileNameOnly = [System.IO.Path]::GetFileNameWithoutExtension($Path)
    $logName = $fileNameOnly + ".log"
    $logFile = Join-Path $Env:TEMP -ChildPath $logName

    $args = "/QUIET /INSTALL /NORESTART `"$logFile`" "
    $args += $Arguments

    $iec = (3010)
    $iec += $IgnoreExitCodes

    Install-FromEXE -Path $Path -Arguments $args -IgnoreExitCodes $iec -IgnoreFailures:$IgnoreFailures
}

#####################################################################################################
# Install-FromChocolatey
#####################################################################################################

<#
    .SYNOPSIS
        Installs a Chocolatey package.
    .DESCRIPTION
        Installs a package using Chocolatey in silent mode with no prompts.
    .PARAMETER Name
        The name of the package to install.

#>
function Install-FromChocolatey
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name
    )

    Write-Host $Separator

    Write-Host "Installing chocolatey package $Name"
    Start-ExternalProcess -Path "C:\ProgramData\chocolatey\bin\choco.exe" -Arguments @("install","-y",$Name)

    Write-Host $Separator
}

#####################################################################################################
# Install-FromEXEAsyncWithDevenvKill
#####################################################################################################

<#
    .SYNOPSIS
        Starts an installer asynchronously and waits in the background for rogue child processes
        and kills them after letting them finish.
    .DESCRIPTION
        Visual Studio installers start a number of child processes. Notable amongst them is the devenv.exe
        process that attempts to initialize the VS IDE. Containers do not support UIs so this part hangs.
        There might be other related processes such as msiexec as well that hang. Invariable, these
        child processes complete quite fast, but never exit potentially becuase they are attempting
        to display some UI and hang. This helper function will kick off the installer and then monitor
        the task list to find those child processes by name and then it will kill them.
    .PARAMETER Path
    .PARAMETER StuckProcessNames
    .PARAMETER IgnoreExitCodes
    .PARAMETER IgnoreFailures
    .PARAMETER Arguments
    .PARAMETER WaitMinutes
#>
function Install-FromEXEAsyncWithDevenvKill
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$true)]
        [string[]]$StuckProcessNames,

        [Parameter(Mandatory=$false)]
        [int[]]$IgnoreExitCodes,

        [Parameter()]
        [switch]$IgnoreFailures,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$WaitMinutes = 5,

        [string[]]$Arguments
    )

    Write-Host $Separator

    Trace-Message "Running $Path with $Arguments"

    $process = Start-Process $Path -PassThru -Verbose -NoNewWindow -ArgumentList $Arguments
    $pid = $process.Id
    $pn = [System.IO.Path]::GetFileNameWithoutExtension($Path)

    Trace-Message "Started EXE asynchronously. Process ID is $pid"

    Wait-ForProcess -Process $process -Minutes $WaitMinutes

    Trace-Message "Walking task list and killing any processes in the stuck process list $StuckProcessNames"

    foreach ($stuckProcessName in $StuckProcessNames)
    {
        Stop-ProcessByName -Name $stuckProcessName -WaitBefore 3 -WaitAfter 3
    }

    Trace-Message "Also killing any rogue msiexec processes"

    Stop-ProcessByName -Name "msiexec" -WaitBefore 3 -WaitAfter 3

    Wait-WithMessage -Message "Waiting for process with ID $pid launched from $Path to finish now that children have been killed off" -Minutes 2

    Stop-ProcessByName -Name $pn -WaitBefore 3 -WaitAfter 3

    $ex = $process.ExitCode;

    if ($ex -eq 0)
    {
        Trace-Message "Install from $Path succeeded with exit code 0"
        Write-Host $Separator
        return
    }

    foreach ($iex in $ignoreExitCodes)
    {
        if ($ex -eq $iex)
        {
            Trace-Message "Install from $Path succeeded with exit code $ex"
            Write-Host $Separator
            return;
        }
    }

    Trace-Error "Failed to install from $Path. Process exited with code $ex"

    if (-not $IgnoreFailures)
    {
        throw "CDPXERROR: Failed to install from $Path. Process exited with exit code $ex"
    }
}

#####################################################################################################
# Confirm-PresenceOfVisualStudioErrorLogFile
#####################################################################################################

<#
    .SYNOPSIS
        Throws an exception if a known Visual Studio installation error log file is found.
    .DESCRIPTION
        Visual Studio installers do not exit with appropriate error codes in case of component
        install failures. Often, any errors are indicated by the presence of a non-zero size
        error log file in the TEMP folder. This function checks for the existence of such files
        and throws an exception if any are found.
    .PARAMETER Path
        The folder in which to check for the presence of the error log files. Defaults to $Env:TEMP
    .PARAMETER Filter
        The filename filter to apply to search for error log files.
    .PARAMETER ThrowIfExists
        If set, then fails if an error log file is found on disk even if the size is zero. Defaults to false.
    .PARAMETER ThrowIfNotEmpty
        If set, then fails if an error log file is found on disk and its size is non-zero. Defaults to true.
#>
function Confirm-PresenceOfVisualStudioErrorLogFile
{
    param
    (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$Filter,

        [Parameter(Mandatory = $false)]
        [ValidateNotNullOrEmpty()]
        [string]$Path = $Env:TEMP,

        [Parameter(Mandatory = $false)]
        [switch]$ThrowIfExists = $false,

        [Parameter(Mandatory = $false)]
        [switch]$ThrowIfNotEmpty = $true
    )

    if (Test-Path $Path)
    {
        Trace-Message "Checking if error log files matching the filter $Filter exist in $Path"

        Get-ChildItem -Path $Path -Filter $Filter |
            ForEach-Object
            {
                $file = $_.FullName
                $len = $_.Length

                Trace-Warning "Found error log file $file with size $len"

                if ($ThrowIfExists)
                {
                    throw "CDPXERROR: At least one error log file $file matching $Filter was found in $Path."
                }

                if ($ThrowIfNotEmpty -and ($len -gt 0))
                {
                    throw "At least one non-empty log file $file matching $filter was found in $folder"
                }
            }
    }
    else
    {
        Trace-Warning "Folder $Path does not exist. Skipping checks."
    }
}

#####################################################################################################
# Stop-ProcessByName
#####################################################################################################

<#
    .SYNOPSIS
        Kills all processes with a given name.
    .DESCRIPTION
        Some installers start multiple instances of other applications to perform various
        post-installer or initialization actions. The most notable is devenv.exe. This function
        provides a mechanism to brute force kill all such instances.
    .PARAMETER Name
        The name of the process to kill.
    .PARAMETER WaitBefore
        The optional number of minutes to wait before killing the process. This provides time for
        the process to finish its processes.
    .PARAMETER WaitAfter
        The optional number of minutes to wait after killing the process. This provides time for
        the process to exit and any handles to expire.
#>
function Stop-ProcessByName
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$WaitBefore = 3,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$WaitAfter = 3
    )

    Wait-WithMessage -Message "Waiting for $WaitBefore minutes before killing all processes named $processName" -Minutes $WaitBefore
    &tasklist /v

    $count = 0

    Get-Process -Name $Name -ErrorAction SilentlyContinue |
        ForEach-Object
        {
            $process = $_
            Trace-Warning "Killing process with name $Name and ID $($process.Id)"
            $process.Kill()
            ++$count
        }

    Trace-Warning "Killed $count processes with name $Name"

    Wait-WithMessage -Message "Waiting for $WaitAfter minutes after killing all processes named $Name" -Minutes $WaitAfter

    &tasklist /v
}

#####################################################################################################
# Wait-WithMessage
#####################################################################################################

<#
    .SYNOPSIS
        Performs a synchronous sleep.
    .DESCRIPTION
        Some asynchronous and other operations require a wait time before
        assuming a failure. This function forces the caller to sleep. The sleep is
        performed in 1-minute intervals and a message is printed on each wakeup.
    .PARAMETER Message
        The message to print after each sleep period.
    .PARAMETER Minutes
        The number of minutes to sleep.
#>
function Wait-WithMessage
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Message,

        [Parameter(Mandatory=$true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Minutes
    )

    $elapsed = 0

    while ($true)
    {
        if ($elapsed -ge $Minutes)
        {
            Write-Host "Done waiting for $elapsed minutes"
            break
        }

        Trace-Message $Message
        Start-Sleep -Seconds 60
        ++$elapsed
    }
}

#####################################################################################################
# Wait-WithMessageAndMonitor
#####################################################################################################

<#
    .SYNOPSIS
        Performs a synchronous sleep and on each wakeup runs a script block that may contain some
        monitoring code.
    .DESCRIPTION
        Some asynchronous and other operations require a wait time before
        assuming a failure. This function forces the caller to sleep. The sleep is performed
        in 1-minute intervals and a message is printed and a script block is run on each wakeup.
    .PARAMETER Message
        The message to print after each sleep period.
    .PARAMETER Block
        The script block to run after each sleep period.
    .PARAMETER Minutes
        The number of minutes to sleep.
#>
function Wait-WithMessageAndMonitor
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Message,

        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [ScriptBlock]$Monitor,

        [Parameter(Mandatory=$true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Minutes
    )

    $elapsed = 0

    while ($true)
    {
        if ($elapsed -ge $Minutes)
        {
            Write-Host "Done waiting for $elapsed minutes"
            break
        }

        Trace-Message $Message
        Start-Sleep -Seconds 60
        $Monitor.Invoke()
        ++$elapsed
    }
}

#####################################################################################################
# Reset-TempFolders
#####################################################################################################

<#
    .SYNOPSIS
        Deletes the contents of well known temporary folders.
    .DESCRIPTION
        Installing lots of software can leave the TEMP folder built up with crud. This function
        wipes the well known temp folders $Env:TEMP and C:\Windows\TEMP of all contentes. The
        folders are preserved however.
#>
function Reset-TempFolders
{
    try
    {
        Trace-Message "Wiping contents of the $($Env:TEMP) and C:\Windows\TEMP folders."

        Get-ChildItem -Directory -Path $Env:TEMP |  ForEach-Object {
                $p = $_.FullName
                Trace-Message "Removing temporary file $p"
                Remove-Item -Recurse -Force -Path $p -ErrorAction SilentlyContinue
            }

        Get-ChildItem -File -Path $Env:TEMP | ForEach-Object {
                $p = $_.FullName
                Trace-Message "Removing temporary file $p"
                Remove-Item -Force -Path $_.FullName -ErrorAction SilentlyContinue
            }

        Get-ChildItem -Directory -Path "C:\Windows\Temp" | ForEach-Object {
                $p = $_.FullName
                Trace-Message "Removing temporary file $p"
                Remove-Item -Recurse -Force -Path $_.FullName -ErrorAction SilentlyContinue
            }

        Get-ChildItem -File -Path "C:\Windows\Temp" | ForEach-Object {
                $p = $_.FullName
                Trace-Message "Removing temporary file $p"
                Remove-Item -Force -Path $_.FullName -ErrorAction SilentlyContinue
            }
    }
    catch
    {
        Trace-Warning "Errors occurred while trying to clean up temporary folders."
        $_.Exception | Format-List
    }
    finally
    {
        Trace-Message "Cleaned up temporary folders at $Env:TEMP and C:\Windows\Temp"
    }
}

#####################################################################################################
# Get-SecretFromKeyVault
#####################################################################################################

<#
    .SYNOPSIS
        Retrieves a secret from a KeyVault using an AAD OAuth token.
    .DESCRIPTION
        A function that is expected to be used in global images that enables fetching
        license data to activate software that requires licensing.
    .PARAMETER VaultName
        The vault to fetch from.
    .PARAMETER SecretName
        The name of the secret.
    .PARAMETER OAuthToken
        The OAuth token to use to authenticate to the vault. The host MSI token is passed in for
        global images.
    .PARAMETER SecretDescription
        An optional description of the secret being fetched.
    .PARAMETER ErrorMessage
        An optional error message to display in case of failures.
    .OUTPUTS
        The value of the secret.
#>
function Get-SecretFromKeyVault
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$VaultName,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$OAuthToken,

        [Parameter(Mandatory=$false)]
        [string]$SecretDescription,

        [Parameter(Mandatory=$false)]
        [string]$ErrorMessage
    )

    $fullDescription = "$SecretDescription ('$SecretName' from Vault '$VaultName')"
    $fullErrorMessage = "Unable to retrieve $fullDescription from Key Vault via MSI. $ErrorMessage"
    $keyVaultApiVersion = "?api-version=2016-10-01";
    $secretUrl = "https://${VaultName}.vault.azure.net/secrets/${SecretName}";
    $secretUrl += $keyVaultApiVersion;

    Trace-Message "Attempting to retrieve $fullDescription using OAuth token"
    Trace-Message "Fetching secret from URL $secretUrl"

    $response = Invoke-WebRequest -UseBasicParsing -Uri $secretUrl -Method GET -Header @{Authorization="Bearer $OAuthToken"} | ConvertFrom-Json;

    $secretValue = $response.value;

    if([string]::IsNullOrWhiteSpace($secretValue))
    {
        throw "CDPXERROR: Failed to fetch secret data from vault. $fullErrorMessage"
    }

    Trace-Message "Successfully acquired $fullDescription from Key Vault"
    return $secretValue
}

#####################################################################################################
# Confirm-FileHash
#####################################################################################################

<#
    .SYNOPSIS
        Verifies the content hash of downloaded content.
    .DESCRIPTION
        By default computes the SHA256 hash of downloaded content and compares it against
        a given hash assuming it to be a SHA256 hash as well.
    .PARAMETER FileName
        The name of the file. If the IsFullPath switch is not specified, assumes a file within
        the downloaded content cache.
    .PARAMETER ExpectedHash
        The expected hash value of the content.
    .PARAMETER Algorithm
        The optional hash algorithm to hash. Defaults to SHA256.
    .OUTPUTS
#>
function Confirm-FileHash
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$ExpectedHash,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Algorithm = "sha256"
    )

    Trace-Message "Verifying content hash for file $Path"

    $exists = Test-Path -Path $Path -PathType Leaf

    if (-not $exists)
    {
        throw "CDPXERROR: Failed to find file $Path in order to verify hash."
    }

    $hash = Get-FileHash $Path -Algorithm $Algorithm

    if ($hash.Hash -ne $ExpectedHash)
    {
        throw "File $Path hash $hash.Hash did not match expected hash $expectedHash"
    }
}

#####################################################################################################
# Start-ExternalProcess
#####################################################################################################

<#
    .SYNOPSIS
        Executes an external application
    .DESCRIPTION
        PowerShell does not deal well with applications or scripts that write to
        standard error. This wrapper function handles starting the process,
        waiting for output and then captures the standard output/error streams and
        reports them without writing them to stderr.
    .PARAMETER Path
        The path to the application to run.
    .PARAMETER Arguments
        The array of arguments to pass to the external application.
    .OUTPUTS
        Returns the exit code that the application exited with.
#>
function Start-ExternalProcess
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments
    )

    Trace-Message "Executing application: $Path $Arguments"

    $guid = [System.Guid]::NewGuid().ToString("N")
    $errLogFileName = -join($guid, "-stderr.log")
    $outLogFileName = -join($guid, "-stdout.log")
    $errLogFile = Join-Path -Path $Env:TEMP -ChildPath $errLogFileName
    $outLogFile = Join-Path -Path $Env:TEMP -ChildPath $outLogFileName
    $workDir = [System.IO.Path]::GetDirectoryName($Path)
    [System.Diagnostics.Process]$process = $null

    if (($Arguments -ne $null) -and ($Arguments.Length -gt 0))
    {
        $process = Start-Process -FilePath $Path -ArgumentList $Arguments -NoNewWindow -PassThru -RedirectStandardError $errLogFile -RedirectStandardOutput $outLogFile
    }
    else
    {
        $process = Start-Process -FilePath $Path -NoNewWindow -PassThru -RedirectStandardError $errLogFile -RedirectStandardOutput $outLogFile
    }

    $handle = $process.Handle
    $pid = $process.Id
    $ex = 0

    Trace-Message -Message "Started process from $Path with PID $pid (and cached handle $handle)"

    while ($true)
    {
        Trace-Message -Message "Waiting for PID $pid to exit ..."

        if ($process.HasExited)
        {
            Trace-Message -Message "PID $pid has exited!"
            break
        }

        Sleep -Seconds 60
    }

    Trace-Message "STDERR ---------------------------"
    Get-Content $errLogFile | Write-Host

    Trace-Message "STDOUT ---------------------------"
    Get-Content $outLogFile | Write-Host

    $ex = $process.ExitCode

    if ($ex -eq $null)
    {
        Trace-Warning -Message "The process $pid returned a null or invalid exit code value. Assuming and returning 0"
        $ex = 0
    }
    else
    {
        Trace-Message "Process $pid exited with exit code $ex"
    }

    return $ex
}

#####################################################################################################
# Run-ExternalProcessWithWaitAndKill
#####################################################################################################

<#
    .SYNOPSIS
        Executes an external application, waits for a specified amount of time and then kills it.
    .DESCRIPTION
        Some applications get stuck when running for the first time. This function starts the
        application, then waits and then kills it so that a subsequent run can succeed.
    .PARAMETER Path
        The path to the application to run.
    .PARAMETER Arguments
        The array of arguments to pass to the external application.
    .PARAMETER Minutes
        The amount of time to wait in minutes before killing the external application.
    .OUTPUTS
        The exit code if one is available from the process.
#>
function Run-ExternalProcessWithWaitAndKill
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Path,

        [Parameter(Mandatory=$false)]
        [string[]]$Arguments,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$Monitor,

        [Parameter(Mandatory=$false)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Minutes
    )

    Trace-Message "Executing application: $Path $Arguments. Will wait $Minutes minutes before killing it."

    $guid = [System.Guid]::NewGuid().ToString("N")
    $errLogFileName = -join($guid, "-stderr.log")
    $outLogFileName = -join($guid, "-stdout.log")
    $errLogFile = Join-Path -Path $Env:TEMP -ChildPath $errLogFileName
    $outLogFile = Join-Path -Path $Env:TEMP -ChildPath $outLogFileName
    $workDir = [System.IO.Path]::GetDirectoryName($Path)
    [System.Diagnostics.Process]$process = $null

    if (-not $Arguments)
    {
        $process = Start-Process -FilePath $Path -NoNewWindow -PassThru -RedirectStandardError $errLogFile -RedirectStandardOutput $outLogFile
    }
    else
    {
        $process = Start-Process -FilePath $Path -ArgumentList $Arguments -NoNewWindow -PassThru -RedirectStandardError $errLogFile -RedirectStandardOutput $outLogFile
    }

    $handle = $process.Handle
    $pid = $process.Id
    $ex = 0

    Trace-Message -Message "Started process from $Path with PID $pid (and cached handle $handle)"

    $exited = Wait-ForProcess -Process $process -Minutes $Minutes -Monitor $Monitor

    if (-not $exited)
    {
        Trace-Warning "CDPXERROR: Process with ID $pid failed to exit within $Minutes minutes. Killing it."

        try
        {
            $process.Kill()
            Trace-Warning "Killed PID $pid"
        }
        catch
        {
            Trace-Warning "Exception raised while attempting to kill PID $pid. Perhaps the process has already exited."
            $_.Exception | Format-List
        }
    }
    else
    {
        $ex = $process.ExitCode
        Trace-Message "Application $Path exited with exit code $ex"
    }

    Trace-Message "STDERR ---------------------------"
    Get-Content $errLogFile | Write-Host

    Trace-Message "STDOUT ---------------------------"
    Get-Content $outLogFile | Write-Host

    if ($ex -eq $null)
    {
        Trace-Warning -Message "The process $pid returned a null or invalid exit code value. Assuming and returning 0"
        return 0
    }

    return $ex
}

#####################################################################################################
# Wait-ForProcess
#####################################################################################################

<#
    .SYNOPSIS
        Waits for a previously started process until it exits or there is a timeout.
    .DESCRIPTION
        Waits for a started process until it exits or a certain amount of time has elapsed.
    .PARAMETER Process
        The [System.Process] project to wait for.
    .PARAMETER Minutes
        The amount of time to wait for in minutes.
    .PARAMETER Monitor
        An optional script block that will be run after each wait interval.
#>
function Wait-ForProcess
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNull()]
        [System.Diagnostics.Process]$Process,

        [Parameter(Mandatory=$true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$Minutes = 10,

        [Parameter(Mandatory=$false)]
        [ScriptBlock]$Monitor
    )

    $waitTime = $Minutes

    $handle = $process.Handle
    $pid = $Process.Id

    while ($waitTime -gt 0)
    {
        Trace-Message -Message "Waiting for process with ID $pid to exit in $waitTime minutes."

        if ($Process.HasExited)
        {
            $ex = $Process.ExitCode
            Trace-Message "Process with ID $pid has already exited with exit code $ex"
            return $true
        }

        Sleep -Seconds 60

        if ($Monitor)
        {
            try
            {
                Trace-Message "Invoking monitor script: $Monitor"
                $Monitor.Invoke()
            }
            catch
            {
                Trace-Warning "Exception occurred invoking monitoring script"
                $_.Exception | Format-List
            }
        }

        --$waitTime
    }

    return $false
}

#####################################################################################################
# Trace-Message
#####################################################################################################

<#
    .SYNOPSIS
        Logs an informational message to the console.
    .DESCRIPTION
        Writes a message to the console with the current timestamp and an information tag.
    .PARAMETER Message
        The message to write.
#>
function Trace-Message
{
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Message
    )

    $Message = $Message -replace "##vso", "__VSO_DISALLOWED"
    $timestamp = Get-Date
    Write-Host "[INFO] [$timestamp] $Message"
}

#####################################################################################################
# Trace-Warning
#####################################################################################################

<#
    .SYNOPSIS
        Logs a warning message to the console.
    .DESCRIPTION
        Writes a warning to the console with the current timestamp and a warning tag.
    .PARAMETER Message
        The warning to write.
#>
function Trace-Warning
{
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Message
    )

    $timestamp = Get-Date
    $Message = $Message -replace "##vso", "__VSO_DISALLOWED"
    Write-Host "[WARN] [$timestamp] $Message" -ForegroundColor Yellow
    Write-Host "##vso[task.logissue type=warning]$Message"
}

#####################################################################################################
# Trace-Error
#####################################################################################################

<#
    .SYNOPSIS
        Logs an error message to the console.
    .DESCRIPTION
        Writes an error to the console with the current timestamp and an error tag.
    .PARAMETER Message
        The error to write.
#>
function Trace-Error
{
    param
    (
        [Parameter(Mandatory=$true, Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]$Message
    )

    $timestamp = Get-Date
    $Message = $Message -replace "##vso", "__VSO_DISALLOWED"
    Write-Host "[ERROR] [$timestamp] $Message" -ForegroundColor Red
    Write-Host "##vso[task.logissue type=error]$Message"
}

#####################################################################################################
# Expand-ArchiveWith7Zip
#####################################################################################################

<#
    .SYNOPSIS
        Uses 7-Zip to expand an archive instead of the standard Expand-Archive cmdlet.
    .DESCRIPTION
        The Expand-Archive cmdlet is slow compared to using 7-Zip directly. This function
        assumes that 7-Zip is installed at C:\7-Zip.
    .PARAMETER -Source
        The path to the archive file.
    .PARAMETER -Destination
        The folder to expand into.
    .PARAMETER ToolPath
        The path to where the 7z.exe tool is available.
#>
function Expand-ArchiveWith7Zip
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Source,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Destination,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ToolPath = "C:\7-Zip\7z.exe",

        [Parameter(Mandatory=$false)]
        [switch]$IgnoreFailures=$false
    )

    Write-Host $Separator

    if (-not $ToolPath)
    {
        throw "CDPXERROR: The 7-Zip tool was not found at $ToolPath."
    }

    if (-not (Test-Path $Source))
    {
        throw "CDPXERROR: The specified archive file $Source could not be found."
    }

    if (-not $Destination)
    {
        $sourceDir = [System.IO.Path]::GetDirectoryName($Source);
        $Destination = $sourceDir

        Trace-Message "No destination was specified so the default location $Destination was chosen."
    }

    Trace-Message "Uncompressing archive $Source into folder $Destination using 7-Zip at $ToolPath"

    Install-FromEXE -Path $ToolPath -Arguments "x -aoa -y `"$Source`" -o`"$Destination`"" -IgnoreFailures:$IgnoreFailures

    Trace-Message "Successfully uncompressed archive at $Source into $Destination"
    Write-Host $Separator
}

#####################################################################################################
# Get-VisualStudioProductKeyArguments
#####################################################################################################

<#
    .SYNOPSIS
        Retrieves a string that can be passed into Visual Studio installations for activating
        the product.
    .DESCRIPTION
        This function returns the full command to pass into Visual Studio installer for
        using a product key to activate the product. The returned string can be passed in directly
        and will be of the form --productKey KEY. The returned value has a leading space but no
        trailing space.
    .PARAMETER SecretName
        If the product key is being retrieved from KeyVault, then the name of the secret that
        holds the required key.
    .OUTPUTS
        The arguments to pass to the Visual Studio command line installer.
#>
function Get-VisualStudioProductKeyArguments
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$SecretName
    )

    $vsProductKey = $Env:TEMP_VS_PRODUCT_KEY
    $productKeyArgs = $null
    $ctrHost = $Env:TEMP_CONTAINER_HOST_NAME

    if ([string]::IsNullOrWhiteSpace($vsProductKey))
    {
        $vsProductKey = Get-SecretFromKeyVault -VaultName "cxprod-basic" -SecretName $SecretName -OAuthToken $Env:TEMP_MSI_OAUTH_TOKEN -SecretDescription "Visual Studio Volume Activation Key"
    }

    if ($vsProductKey -eq "")
    {
        Trace-Warning -Message "Using an empty product key. Visual Studio installation will not be activated."
    }
    else
    {
        $productKeyArgs = " --productKey $vsProductKey"
    }

    Write-Host "##vso[task.setvariable variable=VS_PRODUCT_KEY_SECRET;issecret=true]$vsProductKey"
    return $productKeyArgs
}

#####################################################################################################
# Get-BlobPackageFromBase
#####################################################################################################

<#
    .SYNOPSIS
        Uses AzCopy to download a blob package from blob store.
    .DESCRIPTION
        Some very large content such as Visual Studio offline installer files are stored in
        a CDPX hosted blob store. This method fetches the contents of such blob packages
        using AzCopy.
#>
function Get-BlobPackageFromBase
{
    param
    (
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ContainerName,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$nodePath,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$downloadPath="C:\Downloads"

    )

    Write-Host $Separator

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $Env:AZCOPY_LOG_LOCATION = $Env:TEMP

    $url = Get-BlobPackageBaseUrl -ContainerName $ContainerName

    Trace-Message "Invoking AzCopy CLI to download package $Name version $Version to $Path from $url"

    $Arguments = @("copy", $url, $downloadPath, "--recursive", "--include-path $nodePath", "--include-pattern *")

    Run-ExternalProcessWithWaitAndKill -Path "C:\AzCopy\azcopy.exe" -Arguments $Arguments -Minutes 30

    Trace-Message "Finished downloading blob package"

    Write-Host $Separator

    return $Path
}

#####################################################################################################
# Get-BlobPackageFromEdge
#####################################################################################################

<#
    .SYNOPSIS
        Uses a HTTP/S request to download a blob package from CDN.
    .DESCRIPTION
        Some content such as third party OSS or free software are hosted on a CDPX hosted
        blob store which is replicated to a CDN. This function fetches the blob package from
        the CDN.
#>
function Get-BlobPackageFromEdge
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Version,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$FileName,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$Path="C:\Downloads",

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ContainerName
    )

    Write-Host $Separator

    [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12

    $url = Get-BlobPackageEdgeUrl -Name $Name -Version $Version -Container $ContainerName

    Trace-Message "Downloading blob package $Name and $Version from $url"

    $path = Get-File -Url $url -FileName $FileName

    Trace-Message "Finished downloading blob package to $FileName"

    Write-Host $Separator

    return $path
}

#####################################################################################################
# Enum HostEnvironment
#####################################################################################################

enum HostEnvironment
{
    Dev
    Test
    Prod
}

#####################################################################################################
# Get-HostEnvironment
#####################################################################################################

<#
    .SYNOPSIS
        Uses some heuristics about the underlying host to determine what kind of environment the
        host is in.
    .DESCRIPTION
        Leverages CDPX host naming conventions to determine if a host is a test or production host. If
        neither is true, this function always assumes that the host is a developer box.
    .OUTPUTS
        An instance of the enumeration HostEnvironment.
#>
function Get-HostEnvironment
{
    $ctrHost = $Env:TEMP_CONTAINER_HOST_NAME

    if ($ctrHost)
    {
        if ($ctrHost.StartsWith("XWT"))
        {
            Trace-Message -Message "Running on CDPX test host."
            return [HostEnvironment]::Test
        }
        elseif ($ctrHost.StartsWith("XWP"))
        {
            Trace-Message -Message "Running on CDPX prod host."
            return [HostEnvironment]::Prod
        }
    }

    Trace-Message "Unsure what kind of CDPX environment underlying host `"$ctrHost`" is in. Assuming development box."
    return [HostEnvironment]::Dev
}

#####################################################################################################
# Get-BlobContainerName
#####################################################################################################

<#
    .SYNOPSIS
        Returns the container name to use for blob packages.
    .DESCRIPTION
        Returns a OS specific container name within which blob packages specific to that OS are
        stored.
    .OUTPUTS
        Returns a lower case string that is the container name within the blob store in which
        blob packages are stored.
#>
function Get-BlobContainerName
{
    if ($Env:os -eq "Windows_NT")
    {
        return "windows"
    }
    elseif ($Env:OS -eq "Linux")
    {
        return "linux"
    }

    throw "CDPXERROR: Only supported operating systems are Windows and Linux. Unknown OS $($Env:OS)"
}

#####################################################################################################
# Get-BlobAccountName
#####################################################################################################

<#
    .SYNOPSIS
        Returns the base storage account in which blob packages are stored.
    .DESCRIPTION
        Returns an environment specific base storage account in which blob packages are stored.
    .OUTPUTS
        Returns a string that is an environment specific value for the blob storage account
        in which blob packages are stored.
#>
function Get-BlobAccountName
{
    $hostEnv = Get-HostEnvironment
    $hostEnvStr = $hostEnv.ToString().ToLowerInvariant()
    $prefix = "cxswdist"
    $accountName = $prefix + $hostEnvStr

    Trace-Warning "Currently overriding blob storage account to cxswdisttest for all host environments."
    return "cxswdisttest"
}

#####################################################################################################
# Get-BlobPackageEdgeUrl
#####################################################################################################

<#
    .SYNOPSIS
        Gets the Azure blob store URL for a given blob package and version.
    .DESCRIPTION
        CDPX hosts some well known packages in blob store. This ensures that these packages
        are always available. This method returns the blob store to use that is appropriate for
        the executing environment.
    .NOTE
        This function is for use with AzCopy.
    .OUTPUTS
        The blob store URL for the package.
#>
function Get-BlobPackageEdgeUrl
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Version,

        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ContainerName
    )

    $packageFullName = Get-PackageFullName -Name $Name -Version $Version
    if ($ContainerName -eq "")
    {
      $containerName = Get-BlobContainerName
    }
    $accountName = Get-BlobAccountName

    $edgeUrl = -join("https://", $accountName, ".azureedge.net", "/", $containerName, "/", $packageFullName)
    return $edgeUrl
}

#####################################################################################################
# Get-BlobPackageBaseUrl
#####################################################################################################

<#
    .SYNOPSIS
        Gets the CDN URL for a given blob package and version.
    .DESCRIPTION
        The binary blob store that CDPX uses is replicated to an anonymous CDN. This enables
        packages to be fetched much faster. This function returns the CDN URL to use.
    .NOTE
        This is only valid for actual blobs; not for containers that can be used with AzCopy.
    .OUTPUTS
        The CDN URL for this package.
#>
function Get-BlobPackageBaseUrl
{
    param
    (
        [Parameter(Mandatory=$false)]
        [ValidateNotNullOrEmpty()]
        [string]$ContainerName
    )

    if ($ContainerName -eq "")
    {
      $containerName = Get-BlobContainerName
    }

    $accountName = Get-BlobAccountName

    $edgeUrl = -join("https://", $accountName, ".blob.core.windows.net", "/", $containerName)
    return $edgeUrl
}

#####################################################################################################
# Get-PackageFullName
#####################################################################################################

<#
    .SYNOPSIS
        Gets the full name of a blob or universal package that can be downloaded by the functions
        in this module.
    .DESCRIPTION
        Given a package name and a version, returns a full name to the package for use with
        AzCopy or Az UPack CLI. The returned version is packagename-packageversion in lower case.
    .OUTPUTS
        The name of the package to use with blob store.
#>

function Get-PackageFullName
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Name,

        [Parameter(Mandatory=$true)]
        [ValidateNotNullOrEmpty()]
        [string]$Version
    )

    $packageFullName = -join($Name, "-", $Version)
    return $packageFullName.ToLowerInvariant()
}

#####################################################################################################
# Get-LatestInstalledNetFrameworkVersion
#####################################################################################################

<#
    .SYNOPSIS
        Gets the latest installed version of the .NET Framework.
    .DESCRIPTION
        Retrieves information from the registry based on the documentation at this link:
        https://docs.microsoft.com/en-us/dotnet/framework/migration-guide/how-to-determine-which-versions-are-installed#net_b.
        Returns the entire child object from the registry.
    .OUTPUTS
        The child registry entry for the .NET framework installation.
#>
function Get-LatestInstalledNetFrameworkVersion
{
    Trace-Message -Message "Retrieving latest installed .NET Framework version from registry entry: HKLM:`\SOFTWARE`\Microsoft`\NET Framework Setup`\NDP`\v4`\Full"

    $item = Get-ChildItem HKLM:"\SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full"

    return $item
}

#####################################################################################################
# Run-VisualStudioInstallerProcessMonitor
#####################################################################################################

<#
    .SYNOPSIS
        Monitors progress of Visual Studio installation.
    .DESCRIPTION
        Checks if VS installer processes named vs_installer or vs_enteprise are still running.
        Returns true if processes with those names were found. Otherwise returns false. In addition,
        lists all dd_setup* log files found in $Env:TEMP where VS installers traditionally place
        log files. Finally, if any error log files are present, prints out the contents of those
        files.
    .OUTPUTS
        True if VS installer or bootstrapper processes are still running. Otherwise false.
#>
function Run-VisualStudioInstallerProcessMonitor
{
    Write-Host $Separator

    $processes = Get-Process
    $numTotalProcesses = 0
    $numVSIProcesses = 0

    $processes | ForEach-Object {

        $process = $_
        $handle = $process.Handle
        $pid = $process.Id
        $ppath = $process.Path

        if ($process.Name.StartsWith("vs_installer") -or
            $process.Name.StartsWith("vs_enterprise"))
        {
            $numVSIProcesses++

            Trace-Message -Message "Found VS Installer process with PID $pid launched from $ppath"
        }

        ++$numTotalProcesses
    }

    Trace-Message "Total processes: $numTotalProcesses. VS Installer processes: $numVSIProcesses"

    $setupLogs = Get-ChildItem $Env:TEMP -Filter "dd_setup*.log"
    $setupLogs | Write-Host

    $setupLogs | ForEach-Object {

        $setupLog = $_
        $setupLogPath = $setupLog.FullName

        if ($setupLog.Name.Contains("errors"))
        {
            Trace-Message "Contents of VS installer error log: $setupLogPath"
            Get-Content -Path $setupLogPath | Write-Host
        }
    }

    Write-Host $Separator

    if ($numVSIProcesses -gt 0)
    {
        return $true
    }

    return $false
}

#####################################################################################################
# Monitor-VisualStudioInstallation
#####################################################################################################

<#
#>
function Monitor-VisualStudioInstallation
{
    param
    (
        [Parameter(Mandatory=$true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$WaitBefore,

        [Parameter(Mandatory=$true)]
        [ValidateRange(1, [int]::MaxValue)]
        [int]$WaitAfter
    )

    $minutes = $WaitBefore

    while ($minutes -gt 0)
    {
        Trace-Message -Message "WAITING for VS installer kickoff."

        Run-VisualStudioInstallerProcessMonitor

        Sleep -Seconds 60

        --$minutes
    }

    $minutes = $WaitAfter

    while ($minutes -gt 0)
    {
        Trace-Message -Message "WAITING for VS installer kickoff."

        $ex = Run-VisualStudioInstallerProcessMonitor

        if (-not $ex)
        {
            Trace-Message -Message "DONE Looks like VS installer processes are no longer running."
            break
        }

        Sleep -Seconds 120

        --$minutes
    }
}


text:
interface ModalOptions {
    backdrop?: boolean|string;
    keyboard?: boolean;
    show?: boolean;
    remote?: string;
}

interface ModalOptionsBackdropString {
    backdrop?: string; // for "static"
    keyboard?: boolean;
    show?: boolean;
    remote?: string;
}

interface ScrollSpyOptions {
    offset?: number;
    target?: string;
}

interface TooltipOptions {
    animation?: boolean;
    html?: boolean;
    placement?: string | Function;
    selector?: string;
    title?: string | Function;
    trigger?: string;
    template?: string;
    delay?: number | Object;
    container?: string | boolean;
    viewport?: string | Function | Object;
}

interface PopoverOptions {
    animation?: boolean;
    html?: boolean;
    placement?: string | Function;
    selector?: string;
    trigger?: string;
    title?: string | Function;
    template?: string;
    content?: any;
    delay?: number | Object;
    container?: string | boolean;
    viewport?: string | Function | Object;
}

interface CollapseOptions {
    parent?: any;
    toggle?: boolean;
}

interface CarouselOptions {
    interval?: number;
    pause?: string;
    wrap?: boolean;
    keybord?: boolean;
}

interface TypeaheadOptions {
    source?: any;
    items?: number;
    minLength?: number;
    matcher?: (item: any) => boolean;
    sorter?: (items: any[]) => any[];
    updater?: (item: any) => any;
    highlighter?: (item: any) => string;
}

interface AffixOptions {
    offset?: number | Function | Object;
    target?: any;
}

interface TransitionEventNames {
    end: string;
}

interface JQuery {
    modal(options?: ModalOptions): JQuery;
    modal(options?: ModalOptionsBackdropString): JQuery;
    modal(command: string): JQuery;

    dropdown(): JQuery;
    dropdown(command: string): JQuery;

    scrollspy(command: string): JQuery;
    scrollspy(options?: ScrollSpyOptions): JQuery;

    tab(): JQuery;
    tab(command: string): JQuery;

    tooltip(options?: TooltipOptions): JQuery;
    tooltip(command: string): JQuery;

    popover(options?: PopoverOptions): JQuery;
    popover(command: string): JQuery;

    alert(): JQuery;
    alert(command: string): JQuery;

    button(): JQuery;
    button(command: string): JQuery;

    collapse(options?: CollapseOptions): JQuery;
    collapse(command: string): JQuery;

    carousel(options?: CarouselOptions): JQuery;
    carousel(command: string): JQuery;

    typeahead(options?: TypeaheadOptions): JQuery;

    affix(options?: AffixOptions): JQuery;

    emulateTransitionEnd(duration: number): JQuery;
}

interface JQuerySupport {
    transition: boolean | TransitionEventNames;
}

declare module "bootstrap" {
}