FreshBuild.psm1

#Region '.\Private\Find-Sources.ps1' 0
function Find-Sources{
    param(
        [PSObject]$Item=$null
    )

    $scoopResult = & scoop search $item.name
    $chocoResult = & choco search $item.name
    $winGetResult = & winget search $item.name

}
#EndRegion '.\Private\Find-Sources.ps1' 10
#Region '.\Private\Get-ByChocolatey.ps1' 0
function Get-ByChocolatey {
    param(
        [PSObject]$Item=$null
    )
    $parameters = @("install", $item.name, "-y")

    if($item.parameters){
        $parameters += [string[]]$item.parameters;
    }

    Write-Host ("{0}: Parameters: {1}" -f $Item.name, $parameters)

    if($command) {
        if($item.elevate){
            return Start-Process -Wait -Verb RunAs -FilePath $command -ArgumentList $parameters
        } else {
            return Start-Process -Wait -NoNewWindow -FilePath $command -ArgumentList $parameters
        }
    } else {
        throw ("No command matching {0} was found." -f $item.command);
    }
}
#EndRegion '.\Private\Get-ByChocolatey.ps1' 22
#Region '.\Private\Get-ByCommand.ps1' 0
function Get-ByCommand {
    param(
        [PSObject]$Item=$null
    )
    if(-not $item.command) { throw "Expected command, none provided." }
    $command = (Get-Command $item.command).Source;
    $argList = @()
    if($item.parameters) {
        $argList += $item.parameters;
    }
    if($command) {
        if($item.elevate){
            return Start-Process -Wait -Verb RunAs -FilePath $command -ArgumentList $argList
        } else {
            return Start-Process -Wait -NoNewWindow -FilePath $command -ArgumentList $argList
        }
    } else {
        throw ("No command matching {0} was found." -f $item.command);
    }
}
#EndRegion '.\Private\Get-ByCommand.ps1' 20
#Region '.\Private\Get-ByInstaller.ps1' 0
function Get-ByInstaller {
    param(
        [PSObject]$Item=$null
    )
    $name = $item.Name
    $parameters = $item.parameters;
    $path = Join-Path $downloadFolder -ChildPath $item.fileName

    $destination = Get-Installer $item -Destination $path

    if($destination -ne $path){
        throw "Not downloaded: $path, received $destination";
    }       

    if(Test-Path $path) {            
        Write-Host ("{0}: Installing from {1}." -f $name, $path)

        $stdOut = "./standardOut.txt"
        $stdError = "./standardError.txt";

        Remove-Item $stdError, $stdOut -ErrorAction SilentlyContinue

        $argList = @()

        switch ($Item.command) {
            'msiexec' { 
                $argList = @("/package", $path) + $parameters;
                $path = (Get-Command $Item.command).Source; 
            }                    
            
            default {
                if($parameters) {
                    $argList += $parameters;
                }
            }
        }

    
        if($path){
            if($Item.elevate){
                $process = Start-Process -FilePath $path `
                    -Verb RunAs `
                    -ArgumentList $argList `
                    -RedirectStandardOutput $stdOut `
                    -RedirectStandardError $stdError `
                    -Wait `
                    -PassThru
            } else {
                $process = Start-Process -FilePath $path `
                    -NoNewWindow `
                    -ArgumentList $argList `
                    -RedirectStandardOutput $stdOut `
                    -RedirectStandardError $stdError `
                    -Wait `
                    -PassThru
            }
        } else {
            throw "`$path is not set.";
        }

        if($process.ExitCode -ne 0){
            Write-Host ("{0}: ExitCode: {1}" -f $Item.name, $process.ExitCode)
            Get-Content $stdOut -ErrorAction SilentlyContinue | Write-Host
            Get-Content $stdError -ErrorAction SilentlyContinue | Write-Error
        }

        Write-Host ("{0}: Install of {1} yielded: {2}" -f $name, $path, $process.ExitCode)
    }
}
#EndRegion '.\Private\Get-ByInstaller.ps1' 69
#Region '.\Private\Get-ByScoop.ps1' 0
function Get-ByScoop {
    param(
        [PSObject]$Item=$null
    )
    $parameters = @("-c", $command, "install", $item.name)

    if($item.parameters){
        $parameters += [string[]]$item.parameters;
    }

    if($command) {
        if($item.elevate){
            return Start-Process -Wait -Verb RunAs -FilePath powershell -ArgumentList $parameters
        } else {
            return Start-Process -Wait -NoNewWindow -FilePath powershell -ArgumentList $parameters
        }
    } else {
        throw ("No command matching {0} was found." -f $item.command);
    }
}
#EndRegion '.\Private\Get-ByScoop.ps1' 20
#Region '.\Private\Get-ByScript.ps1' 0
function Get-ByScript {
    param(
        [PSObject]$Item=$null
    )
    $script = $item.script;
    Write-Host ("{0}: Installing..." -f $item.name)
    $argList = @("-c", $script)
    if($item.parameters) {
        $argList += $item.parameters;
    }
    if($item.elevate) {
        try{
            Start-Process -Wait -Verb RunAs -FilePath powershell -ArgumentList $argList
        } catch {
            Write-Error $_
        }
    } else {
        try{
            Start-Process -Wait -NoNewWindow -FilePath powershell -ArgumentList $argList
        } catch {
            Write-Error $_
        }
    }
    Write-Host ("{0}: Installation complete." -f $item.name)
}
#EndRegion '.\Private\Get-ByScript.ps1' 25
#Region '.\Private\Get-ByWinGet.ps1' 0
function Get-ByWinget {
    param(
        [PSObject]$Item=$null
    )
    $parameters = @("install", $item.name)

    if($item.parameters){
        $parameters += [string[]]$item.parameters;
    }

    if($command) {
        if($item.elevate){
            return Start-Process -Wait -Verb RunAs -FilePath $command -ArgumentList $parameters
        } else {
            return Start-Process -Wait -NoNewWindow -FilePath $command -ArgumentList $parameters
        }
    } else {
        throw ("No command matching {0} was found." -f $item.command);
    }
}
#EndRegion '.\Private\Get-ByWinGet.ps1' 20
#Region '.\Private\Get-Installer.ps1' 0
function Get-Installer {
    param (
        [PSObject]$Item=$null,
        [string]$Destination
    )

    if($Item.url -and $Item.url.length -gt 0)
    {
        if(Test-Path $Destination){
            return $Destination;
        }

        Write-Host ("{0}: Downloading {1} to {2}" -f $Item.name, $Item.url, $Destination);
        Invoke-WebRequest $Item.url -OutFile $Destination

        if(-not(Test-Path $Destination)){
            throw ("{0}: Error downloading file." -f $Item.name)
        } else {
            Write-Host ("{0}: Downloaded to: {1}" -f $Item.name, $Destination)
            return $Destination
        }
    }
}

#EndRegion '.\Private\Get-Installer.ps1' 24
#Region '.\Private\Install-Chocolatey.ps1' 0
function Install-Chocolatey {
    $item = ('{
        "name": "Chocolatey",
        "source": "direct",
        "script": "Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString(\\\"https://chocolatey.org/install.ps1\\\"))",
        "elevate": true
        }'
 | ConvertFrom-Json);

    Get-ByScript $item;
}

function Uninstall-Chocolatey {
    $VerbosePreference = 'Continue'
    if (-not $env:ChocolateyInstall) {
        $message = @(
            "The ChocolateyInstall environment variable was not found."
            "Chocolatey is not detected as installed. Nothing to do."
        ) -join "`n"

        Write-Warning $message
        return
    }

    if (-not (Test-Path $env:ChocolateyInstall)) {
        $message = @(
            "No Chocolatey installation detected at '$env:ChocolateyInstall'."
            "Nothing to do."
        ) -join "`n"

        Write-Warning $message
        return
    }

    <#
    Using the .NET registry calls is necessary here in order to preserve environment variables embedded in PATH values;
    Powershell's registry provider doesn't provide a method of preserving variable references, and we don't want to
    accidentally overwrite them with absolute path values. Where the registry allows us to see "%SystemRoot%" in a PATH
    entry, PowerShell's registry provider only sees "C:\Windows", for example.
#>

    $userKey = [Microsoft.Win32.Registry]::CurrentUser.OpenSubKey('Environment')
    $userPath = $userKey.GetValue('PATH', [string]::Empty, 'DoNotExpandEnvironmentNames').ToString()

    $machineKey = [Microsoft.Win32.Registry]::LocalMachine.OpenSubKey('SYSTEM\ControlSet001\Control\Session Manager\Environment\')
    $machinePath = $machineKey.GetValue('PATH', [string]::Empty, 'DoNotExpandEnvironmentNames').ToString()

    $backupPATHs = @(
        "User PATH: $userPath"
        "Machine PATH: $machinePath"
    )
    $backupFile = "C:\PATH_backups_ChocolateyUninstall.txt"
    $backupPATHs | Set-Content -Path $backupFile -Encoding UTF8 -Force

    $warningMessage = @"
    This could cause issues after reboot where nothing is found if something goes wrong.
    In that case, look at the backup file for the original PATH values in '$backupFile'.
"@


    if ($userPath -like "*$env:ChocolateyInstall*") {
        Write-Verbose "Chocolatey Install location found in User Path. Removing..."
        Write-Warning $warningMessage

        $newUserPATH = @(
            $userPath -split [System.IO.Path]::PathSeparator |
                Where-Object { $_ -and $_ -ne "$env:ChocolateyInstall\bin" }
        ) -join [System.IO.Path]::PathSeparator

        # NEVER use [Environment]::SetEnvironmentVariable() for PATH values; see https://github.com/dotnet/corefx/issues/36449
        # This issue exists in ALL released versions of .NET and .NET Core as of 12/19/2019
        $userKey.SetValue('PATH', $newUserPATH, 'ExpandString')
    }

    try {
        if ($machinePath -like "*$env:ChocolateyInstall*") {
            Write-Verbose "Chocolatey Install location found in Machine Path. Removing..."
            Write-Warning $warningMessage

            $newMachinePATH = @(
                $machinePath -split [System.IO.Path]::PathSeparator |
                    Where-Object { $_ -and $_ -ne "$env:ChocolateyInstall\bin" }
            ) -join [System.IO.Path]::PathSeparator

            # NEVER use [Environment]::SetEnvironmentVariable() for PATH values; see https://github.com/dotnet/corefx/issues/36449
            # This issue exists in ALL released versions of .NET and .NET Core as of 12/19/2019
            $machineKey.SetValue('PATH', $newMachinePATH, 'ExpandString')
        }
    }
    catch {
        #Write-Error $_
    }

    # Adapt for any services running in subfolders of ChocolateyInstall
    $agentService = Get-Service -Name chocolatey-agent -ErrorAction SilentlyContinue
    if ($agentService -and $agentService.Status -eq 'Running') {
        $agentService.Stop()
    }
    # TODO: add other services here

    Remove-Item -Path $env:ChocolateyInstall -Recurse -Force

    'ChocolateyInstall', 'ChocolateyLastPathUpdate' | ForEach-Object {
        foreach ($scope in 'User', 'Machine') {
            [Environment]::SetEnvironmentVariable($_, [string]::Empty, $scope)
        }
    }

    $machineKey.Close()
    $userKey.Close()

    if ($env:ChocolateyToolsLocation -and (Test-Path $env:ChocolateyToolsLocation)) {
        Remove-Item -Path $env:ChocolateyToolsLocation -Recurse -Force
    }
    
    foreach ($scope in 'User', 'Machine') {
        [Environment]::SetEnvironmentVariable('ChocolateyToolsLocation', [string]::Empty, $scope)
    }

    Write-Host "Completed uninstallation of Chocolatey."
}
#EndRegion '.\Private\Install-Chocolatey.ps1' 118
#Region '.\Private\Install-Scoop.ps1' 0
function Install-Scoop {
    $item = ('{
        "name": "Scoop",
        "source": "direct",
        "script": "iwr -useb get.scoop.sh | iex; scoop bucket add extras" }'
 | ConvertFrom-Json);

    Get-ByScript $item;
}

function Uninstall-Scoop{
    $scoop = Get-Command scoop;

    if($scoop) {
        . $scoop export | ForEach-Object -process { . $scoop uninstall $_ }
        . $scoop uninstall scoop
    }
}
#EndRegion '.\Private\Install-Scoop.ps1' 17
#Region '.\Private\Install-Software.ps1' 0
function Install-Software{
    param(
        [PSObject]$Item=$null
    )
    Write-Host $Item

    switch($Item.source){
        "winget" {             
            $command= $env:USERPROFILE + "/AppData/Local/Microsoft/WindowsApps/winget.exe";

            if(-not(Test-Path $command)) {
                Install-WinGet
            }

            $result = Get-ByWinget $item 
            if($result) {
                Write-Host $result;
            }
        }

        "scoop" { 
            $command= $env:USERPROFILE + "/scoop/shims/scoop.ps1";

            if(-not(Test-Path $command)) {
                Install-Scoop
            }

            $result = Get-ByScoop $item 
            if($result) {
                Write-Host $result;
            }
        }

        "choco" { 
            $command="C:/ProgramData/chocolatey/bin/cinst.exe";

            if(-not(Test-Path $command)) {
                Install-Chocolatey
            }

            $result = Get-ByChocolatey $item 
            if($result) {
                Write-Host $result;
            }
        }
    
        default {
            try{
                if($item.url.length -gt 0)
                {
                    Get-ByInstaller $item
                } elseif($item.script) {
                    Get-ByScript $item
                } elseif($item.command) {
                    Get-ByCommand $item
                }
            } catch {
                Write-Error $_
                throw $_       
            }
        }
    }
}
#EndRegion '.\Private\Install-Software.ps1' 63
#Region '.\Private\Install-WinGet.ps1' 0
function Install-WinGet {
    $item = ('{
        "name": "WinGet",
        "source": "direct",
        "url": "",
        "script": "Add-AppxPackage https://github.com/microsoft/winget-cli/releases/download/v.0.2.2521-preview/Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.appxbundle -InstallAllResources"
    }'
 | ConvertFrom-Json);

    Get-ByScript $item;

    '{
        // For documentation on these settings, see: https://aka.ms/winget-settings
        // "source": {
        // "autoUpdateIntervalInMinutes": 5
        // },
        "experimentalFeatures": {
            "experimentalMSStore": true
        }
    }'
 | Out-File "$env:USERPROFILE\AppData\Local\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\settings.json"
}

function Uninstall-WinGet {
    $winget = Get-AppxPackage Microsoft.DesktopAppInstaller;
    
    if ($winget) { 
        $winget | Remove-AppxPackage
    }
}
#EndRegion '.\Private\Install-WinGet.ps1' 28
#Region '.\Private\Search-FreshBuild.ps1' 0
function Search-FreshBuild {
    [CmdletBinding()]
    param(
        [string]$jsonConfig = "$env:USERPROFILE/.freshBuild/FreshInstall.json",
        [string]$Exclude = $null,
        [string]$Include = $null,
        [switch]$Search = $false
    )

    Write-Host "Seach: "
    if ($Include) { Write-Host ('Include: {0}' -f $Include) }
    if ($Exclude) { Write-Host ('Exclude: {0}' -f $Exclude) }   
}
#EndRegion '.\Private\Search-FreshBuild.ps1' 13
#Region '.\Public\Initialize-FreshBuild.ps1' 0
function Initialize-FreshBuild {
    param(
        [Switch]$All = $false,
        [switch]$InstallWinGet = $false,
        [switch]$InstallChocolatey = $false,
        [switch]$InstallScoop = $false
    )

    $jsonFile = "$env:USERPROFILE/.freshbuild/PackageManagers.json"

    if (Test-Path $jsonFile) {
        [string[]]$installed = Get-Content $jsonFile | ConvertFrom-Json;
    }
    else {
        [string[]]$installed = @();
    }

    if ($InstallWinGet -or $All) { Install-WinGet; $installed += "WinGet" }
    if ($InstallChocolatey -or $All) { Install-Chocolatey; $installed += "Chocolatey" }
    if ($InstallScoop -or $All) { Install-Scoop; $installed += "Scoop" }

    $json = $installed | ConvertTo-Json; 
    if (-not $json) {
        $json = "[]";
    }
    $json | Out-File -Path $jsonFile
}
#EndRegion '.\Public\Initialize-FreshBuild.ps1' 27
#Region '.\Public\Start-FreshBuild.ps1' 0
function Start-FreshBuild {
    [CmdletBinding()]
    param(
        [string]$jsonConfig = "$env:USERPROFILE/.freshBuild/FreshInstall.json",
        [string]$Exclude = $null,
        [string]$Include = $null,
        [switch]$Step = $false,
        [switch]$UninstallWinGet=$false,
        [switch]$UninstallChocolatey=$false,
        [switch]$UninstallScoop=$false,
        [switch]$InstallWinGet=$false,
        [switch]$InstallChocolatey=$false,
        [switch]$InstallScoop=$false,
        [switch]$NoScript=$false,
        [switch]$UpdateJson=$false
    )

    Push-Location

    try {
        $jsonUrl = "https://gist.github.com/sharpninja/2ad839cb141bc6b968278bd7416931ce/raw/"
        $defaultJsonConfig = "$env:USERPROFILE/.freshBuild/FreshInstall.json";

        if($UpdateJson) {
            Invoke-WebRequest `
            -Uri $jsonUrl `
            -OutFile $defaultJsonConfig                
        }

        if (-not (Test-Path $jsonConfig)) {
            if ($jsonConfig -eq $defaultJsonConfig) {
                Set-Location (Get-Module -Name "FreshBuild").Path
                New-Item $env:USERPROFILE/.freshBuild -ItemType Directory -ErrorAction SilentlyContinue
                Invoke-WebRequest `
                -Uri $jsonUrl `
                -OutFile $defaultJsonConfig                
            }
            else {
                return -1;
            }
        }

        $items = (Get-Content $jsonConfig | ConvertFrom-Json).items

        $downloadFolder = "$env:USERPROFILE/.freshBuild/downloads"

        if($UninstallWinGet) {Uninstall-WinGet}
        if($UninstallChocolatey) {Uninstall-Chocolatey}
        if($UninstallScoop) {Uninstall-Scoop}

        if($InstallWinGet) {Install-WinGet}
        if($InstallChocolatey) {Install-Chocolatey}
        if($InstallScoop) {Install-Scoop}

        if($NoScript) {return 0;}

        if (-not (Test-Path $downloadFolder)) {
            New-Item $downloadFolder -ItemType Directory
        }

        foreach ($item in $items) {
            Write-Host $item.name
            if (-not ($item -is [string])) {
                if ((-not($Exclude -and ($item.name -Match $Exclude))) -and 
                    (($null -eq $Include) -or 
                        ($item.name -match $Include))) {
                    Write-Host ("{0}: Beginning Installation." -f $item.name)
                    [PSObject]$typed = $item;
                    Install-Software -Item $typed
                    Write-Host ("{0}: Finished Installation." -f $item.name)
                    Write-Host
                }
            }
            if ($Step) {
                Read-Host "Hit enter for next step..."
            }
        }
    }
    finally {
        Pop-Location
    }
}

#EndRegion '.\Public\Start-FreshBuild.ps1' 83