public/Invoke-VSCodeInstaller.ps1

# Requires -RunAsAdministrator
function Invoke-VSCodeInstaller {
    <#
    .SYNOPSIS
        Installs and sets up Visual Studio Code.
    .DESCRIPTION
        Invoke-VSCodeInstaller downloads, installs and sets up Visual Studio Code and Git as your coding
        environment. You can skip the Git installation by selecting parameter SkipGitInstallation.
 
        If template 'PowerShell Core' is selected it additionally downloads and installs PowerShell Core, the
        VSCode extensions PowerShell and ShellLauncher and sets PowerShell as the default language in VSCode.
        The ShellLauncher is automatically configured with a shell entry for PowerShell, PowerShellCore, cmd and
        Git bash.
 
        If template 'PowerShell' is selected it additionally downloads and installs the VSCode extension PowerShell
        and sets PowerShell as the default language in VSCode.
    .PARAMETER Template
        The name of the template to use with the Invoke-VSCodeInstaller command. To see a list of available
        templates use the Get-VSCodeInstallTemplate command.
 
        You can define your own templates in the VSCodeTemplateData.psd1 file located in the VSCodeInstaller
        module folder.
    .PARAMETER InstallerType
        Specifies if an User oder System Visual Studio Code setup will be downloaded and installed. The
        InstallerType User does not require administrator privileges for installation as the install location will
        be under your user local AppData ($env:LOCALAPPDATA) folder. User setup also provides a smoother background
        update experience.
    .PARAMETER Architecture
        Specifies if the 32-bit or 64-bit Visual Studio Code and Git setup will be downloaded and installed
        provided no SkipGitInstallation option is selected. If Template 'PowerShellCore' is selected, it also
        specifies if the 32-bit oder 64-bit version of the PowerShell Core for Windows will be downloaded and
        installed. Default value is 64-bit.
    .PARAMETER Build
        Specifies if the insider (preview) version or the stable version of the Visual Studio Code setup file will
        be downloaded and installed. If Template 'PowerShellCore' is selected, it also specifies if the preview
        version or the stable version of PowerShell Core for Windows will be downloaded and installed. Default
        value is stable.
    .PARAMETER AdditionalExtensions
        Specifies the full name of the extenisions to install. An array of extension names is accepted.
        Define the full name in the following way: <publisher name>.<extension name>, for example ms-python.python.
 
        To find the full extension name go the Extensions panel in Visual Studio Code. The full name is located
        on the right to the extension name.
 
        Alternatively, the full name can also be found in the Visual Studio Marketplace URI of the extension, for
        example: https://marketplace.visualstudio.com/itemdetails?itemName=ms-python.python
    .PARAMETER SkipGitInstallation
        Specifies if Git will be installed or not. If SkipGitInstallation is selected, Git will not be installed.
    .PARAMETER GitUserName
        Specifies the Git user name for commit information. The user name is globally set in the first-time Git
        setup routine.
    .PARAMETER GitUserEmail
        Specifies the Git user email for commit information. The user email is globally set in the first-time Git
        setup routine.
    .EXAMPLE
        Invoke-VSCodeInstaller -Verbose
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user stable
        version and Git for Windows 64-bit version. Git will be configured with the standard username 'gituser'
        and standard user email 'gituser@example.com' globally for the first-time Git setup. The example uses the
        Verbose parameter to display extended installation information like installation return codes.
    .EXAMPLE
        Invoke-VSCodeInstaller -SkipGitInstallation
        This command only downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user
        stable version.
    .EXAMPLE
        Invoke-VSCodeInstaller -Architecture 'x86' -Build 'insider'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 32-bit user insider
        version and the latest Git for Windows 32-bit version.
    .EXAMPLE
        Invoke-VSCodeInstaller -GitUserName 'example' -GitUserEmail 'example@example.com'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user stable
        version and the latest Git for Windows 64-bit version and uses user name and user email
        'example@example.com' globally for the first-time Git setup.
    .EXAMPLE
        Invoke-VSCodeInstaller -AdditionalExtensions @('ahmadawais.shades-of-purple', 'hoovercj.vscode-power-mode')
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user stable
        version and the latest Git for Windows 64-bit version and the VSCode extensions 'shades-of-purple'and
        'vscode-power-mode'.
    .EXAMPLE
        Invoke-VSCodeInstaller -Template 'PowerShell'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user stable
        version and the latest Git for Windows 64-bit version. When Template 'PowerShell' is selected, it will
        also download and install the VSCode PowerShell extension and sets PowerShell as the default language in
        VSCode.
    .EXAMPLE
        Invoke-VSCodeInstaller -Template 'PowerShellCore' -GitUserName 'example' -GitUserEmail 'example@example.com'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit user stable
        version and the latest Git for Windows 64-bit version and uses user name 'example' and user email
        'example@example.com' globally for the first-time Git setup. When Template 'PowerShellCore' is selected,
        it will also download and install the VSCode PowerShell extension, the Tyriar Shell Launcher extension and
        the latest PowerShell Core 64-bit stable version and sets PowerShell as the default language in VSCode.
        The ShellLauncher extension will be configured with a shell entry for PowerShell, PowerShell Core, cmd and
        Git bash.
    .EXAMPLE
        Invoke-VSCodeInstaller -Template 'PowerShellCore' -Architecture 'x86'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 32-bit user stable
        version and the latest Git for Windows 32-bit version and uses user name 'example' and user email
        'example@example.com' globally for the first-time Git setup. When Template 'PowerShellCore' is selected,
        it will also download and install the VSCode PowerShell extension, the Tyriar Shell Launcher extension and
        the latest PowerShell Core 32-bit stable version and sets PowerShell as the default language in VSCode.
        The ShellLauncher extension will be configured with a shell entry for PowerShell, PowerShell Core, cmd and
        Git bash.
    .EXAMPLE
        Invoke-VSCodeInstaller -InstallerType 'System' -GitUserName 'example' -GitUserEmail 'example@example.com'
        This command downloads and installs the latest Visual Studio Code setup for Windows as 64-bit system
        stable version and the latest Git for Windows 64-bit version and uses user name 'example' and user email
        'example@example.com' globally for the first-time Git setup.
    .LINK
        https://github.com/zoodeploy/vscodeinstaller/blob/master/docs/Invoke-VSCodeInstaller.md
    #>

    [CmdletBinding(
        DefaultParameterSetName='None',
        HelpURI='https://github.com/zoodeploy/vscodeinstaller/blob/master/docs/Invoke-VSCodeInstaller.md'
    )]
    [OutputType([System.String])]
    [Alias('ivsc')]
    param (
        [System.String] $Template,

        [ValidateSet('User', 'System')]
        [System.String] $InstallerType = 'User',

        [ValidateSet('x64', 'x86')]
        [System.String] $Architecture = 'x64',

        [ValidateSet('Stable', 'Insider')]
        [System.String] $Build = 'Stable',

        [System.String[]] $AdditionalExtensions,

        [Parameter(ParameterSetName='SkipGit')]
        [System.Management.Automation.SwitchParameter] $SkipGitInstallation,

        [Parameter(ParameterSetName='InstallGit')]
        [System.String] $GituserName = 'gituser',

        [Parameter(ParameterSetName='InstallGit')]
        [System.String] $GitUserEmail = 'gituser@example.com'
    )

    $installQueue = New-Object System.Collections.Queue
    $removeDownloadedFiles = @()

    if ($PSBoundParameters.ContainsKey('Template')) {
        $ildParam = @{
            BindingVariable = 'VSCodeTemplateData'
            BaseDirectory = ($PSScriptRoot -split '\\public')[0]
            FileName = 'VSCodeTemplateData.psd1'
            ErrorAction = 'SilentlyContinue'
        }
        Import-LocalizedData @ildParam

        ## Add extensions from template
        if ($VSCodeTemplateData) {
            $data = $VSCodeTemplateData.TemplateData | Where-Object Name -eq $Template -ErrorAction SilentlyContinue
            if ($data) {
                if ($AdditionalExtensions) {
                    $templateExtensions = Compare-Object $data.Extensions $AdditionalExtensions -PassThru
                    $AdditionalExtensions += $templateExtensions
                }
                else {
                    $AdditionalExtensions += $data.Extensions
                }
            }
            else {
                Write-Verbose ($localized.TemplateNotFound -f $Template)
            }
        }
        else {
            Write-Verbose $localized.ImportTemplateError
        }
        ## Add PSCore to install queue if not already installed
        if ($Template -eq 'PowerShellCore') {
            $regPath = @(
                'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\',
                'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\'
            )
            $PSCore = Get-ChildItem $regPath  | Get-ItemProperty | Select-Object DisplayName
            $PSCore = $PSCore | Where-Object DisplayName -match 'PowerShell [6-99]'

            if (-not $PSCore) {
                $installQueue.enqueue('installPSCore')
            }
            else {
                Write-Verbose ($localized.SoftwareIsAlreadyInstalled -f $localized.PSCore)
            }
        }
    }

    $vscodeCmd = @(
        "$env:SYSTEMDRIVE\Program Files*\Microsoft VS Code*\bin\code*.cmd",
        "$env:LOCALAPPDATA\Programs\Microsoft VS Code*\bin\code*.cmd"
    )
    $isVSCodeInstalled = Resolve-Path $vscodeCmd -ErrorAction SilentlyContinue

    ## Add VSCode to install queue if not already installed
    if (-not ($isVSCodeInstalled)) {
        $installQueue.enqueue('installVSCode')
    }
    else {
        Write-Verbose ($localized.SoftwareIsAlreadyInstalled -f $localized.VSCode)
    }

    ## Add Git to install queue if not already installed
    if (-not ($SkipGitInstallation)) {
        $pathGit = "$env:SYSTEMDRIVE\Program Files*\Git\git-cmd.exe"
        if (-not (Test-Path $pathGit -PathType Leaf)) {
            $installQueue.enqueue('installGit')
        }
        else {
            Write-Verbose ($localized.SoftwareIsAlreadyInstalled -f $localized.Git)
        }
    }
    else {
        Write-Verbose ($localized.SkipGitInstallation)
    }

    ## Calculating Write-Progress steps
    $currentStep = 1
    $totalSteps = ($installQueue.Count * 2)
    if ($AdditionalExtensions) {
        $totalSteps++
    }
    $wpParam = @{
        Activity = $localized.Activity
        Id = 1
    }

    try {
        while ($installQueue -gt 0) {
            switch ($installQueue.dequeue()) {
                'installVSCode' {
                    $vdParam = @{
                        InstallerType = $InstallerType
                        Architecture = $Architecture
                        Build = $Build
                    }
                    $status = $localized.WriteProgressDownload  -f $currentStep, $totalSteps, $localized.VSCode
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    $vscode = Invoke-VSCodeDownload @vdParam
                    $removeDownloadedFiles += $vscode
                    $status = $localized.WriteProgressInstallation  -f $currentStep, $totalSteps, $localized.VSCode
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    $null = Install-VSCode -FilePath $vscode
                }
                'installGit' {
                    $status = $localized.WriteProgressDownload -f $currentStep, $totalSteps, $localized.Git
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    $git = Invoke-GitDownload -Architecture $Architecture
                    $removeDownloadedFiles += $git
                    $status = $localized.WriteProgressInstallation -f $currentStep, $totalSteps, $localized.Git
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    Install-Git -FilePath $git -UserName $GituserName -UserEmail $GitUserEmail
                }
                'installPSCore' {
                    $status = $localized.WriteProgressDownload  -f $currentStep, $totalSteps, $localized.PSCore
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    if ($Build -eq 'Stable') {
                        $PSCore = Invoke-PSCoreDownload -Architecture $Architecture
                    }
                    else {
                        $PSCore = Invoke-PSCoreDownload -Architecture $Architecture -PreviewVersion
                    }
                    $removeDownloadedFiles += $PSCore
                    $status = $localized.WriteProgressInstallation  -f $currentStep, $totalSteps, $localized.PSCore
                    Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)
                    Install-PSCore -FilePath $PSCore
                }
            }
        }
        ## Install additional VSCode extensions
        if ($AdditionalExtensions) {
            $installedExtensions = Get-InstalledVSCodeExtension
            $status = $localized.WriteProgressInstallation -f $currentStep, $totalSteps, $localized.VSCodeExtensions
            Write-Progress @wpParam -Status $status -PercentComplete ($currentStep++ / $totalSteps * 100)

            $AdditionalExtensions | ForEach-Object {
                $publisher = ($_ -split '\.')[0]
                $name = ($_ -split '\.')[1]

                if (($publisher -in $installedExtensions.Publisher) -and
                   ($name -in $installedExtensions.Name)) {
                    Write-Verbose ($localized.VSCodeExtensionAlreadyInstalled -f $_)
                }
                else {
                    Install-VSCodeExtension -Extension $_
                    ## Set ShellLauncher extension
                    if ($_ -eq 'tyriar.shell-launcher') {
                        $slc = New-ShellLauncherConfiguration
                        Set-VSCodeUserSetting -InputObject $slc
                    }
                    ## Set PowerShell as default language
                    if ($_ -eq 'ms-vscode.powershell') {
                        $psSetDefaultLanguage = @{'files.defaultLanguage' = 'powershell'} | ConvertTo-Json
                        Set-VSCodeUserSetting -InputObject $psSetDefaultLanguage
                    }
                }
            }
        }
    }
    catch {
        throw ($localized.InvokeVSCodeInstallerError -f $_.Exception.Message)
    }
    finally {
        $removeDownloadedFiles | ForEach-Object {
            Remove-Item $_ -Force -ErrorAction SilentlyContinue
        }
        Write-Progress -Activity $localized.Activity -Completed -Id 1
    }
}