ninja-one/winget.ps1

#Requires -Version 5.1

<#
.SYNOPSIS
    Install, Uninstall, or Upgrade a package using WinGet.
.DESCRIPTION
    Install, Uninstall, or Upgrade a package using WinGet.
#>


# RUNNING AS SYSTEM DOES NOT INSTALL MS STORE APPS USING WINGET.EXE

param(
    [Parameter()]
    [String]$Action,
    [Parameter()]
    [String]$PackageId,
    [Parameter()]
    [String]$PackageIdSelect,
    [Parameter()]
    [switch]$MachineScope = [System.Convert]::ToBoolean($env:machineScope)
)

begin {
    # Check for required PowerShell version (7+)
    if (!($PSVersionTable.PSVersion.Major -ge 7)) {
        try {
            # Install PowerShell 7 if missing
            if (!(Test-Path "$env:SystemDrive\Program Files\PowerShell\7")) {
                Write-Output 'Installing PowerShell version 7...'
                Invoke-Expression "& { $( Invoke-RestMethod https://aka.ms/install-powershell.ps1 ) } -UseMSI -Quiet"
            }

            # Refresh PATH
            $env:Path = [System.Environment]::GetEnvironmentVariable('Path', 'Machine') + ';' + [System.Environment]::GetEnvironmentVariable('Path', 'User')

            # Restart script in PowerShell 7
            pwsh -File "`"$PSCommandPath`"" @PSBoundParameters

        }
        catch {
            Write-Output '[ERROR] PowerShell 7 was not installed. Update PowerShell and try again.'
            Write-Error $_
            exit 1
        }
        finally {
            exit $LASTEXITCODE
        }
    }
    else {
        $PSStyle.OutputRendering = 'PlainText'
    }

    function Test-IsElevated {
        $id = [System.Security.Principal.WindowsIdentity]::GetCurrent()
        $p = New-Object System.Security.Principal.WindowsPrincipal($id)
        return $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
    }

    if (-not (Test-IsElevated)) {
        Write-Warning "Many apps require administrator privileges in order to install, uninstall, or upgrade. This action may fail however some apps like Zoom may work."
    }

    # generate timestamp string
    $timestamp = Get-Date -Format "yyyy-MM-dd_HH-mm-ss"

    Start-Transcript -Path "$env:TEMP\ninja_winget-$timestamp.log" -IncludeInvocationHeader

}
process {
    try {
        $silent = "--silent"
        $acceptpackageagreements = "--accept-package-agreements"
        $acceptsourceagreements = "--accept-source-agreements"
        $disableinteractivity = "--disable-interactivity"

        $uninstallArgs = $acceptsourceagreements, $disableinteractivity, $silent
        $installArgs = $silent, $acceptsourceagreements, $disableinteractivity, $acceptpackageagreements

        if ($MachineScope) {
            $installArgs += "--scope", "machine"
            $uninstallArgs += "--scope", "machine"
        }
        else {
            $installArgs += "--scope", "user"
            $uninstallArgs += "--scope", "user"
        }

        $WinGetPath = "$ENV:PROGRAMDATA\JRE-WinGet"

        function InstallWinGet() {
            function BuildWinGet() {
                New-Item -Path $WinGetPath -Name "build" -ItemType "directory" -Force | Out-Null            
    
                $WinGetDepsUrl = ((Invoke-RestMethod -uri 'https://api.github.com/repos/microsoft/winget-cli/releases/latest').assets | Where-Object { $_.browser_download_url.EndsWith('DesktopAppInstaller_Dependencies.zip') } | Select-Object -First 1).browser_download_url
                Invoke-WebRequest $WinGetDepsUrl -OutFile "$WinGetPath/build/Microsoft.DesktopAppInstallerDependencies.zip" -UseBasicParsing
                Expand-Archive -Path "$WinGetPath/build/Microsoft.DesktopAppInstallerDependencies.zip" -DestinationPath "$WinGetPath/build/Microsoft.DesktopAppInstallerDependencies"

                foreach ($file in Get-ChildItem -Path "$WinGetPath/build/Microsoft.DesktopAppInstallerDependencies/x64" -Filter "*.msix") {
                    Rename-Item -Path $file.FullName -NewName ($file.BaseName + ".zip")
                    Expand-Archive -Path "$WinGetPath/build/Microsoft.DesktopAppInstallerDependencies/x64/$($file.BaseName).zip" -DestinationPath "$WinGetPath/build/WinGet" -Force
                }

                $WinGetUrl = ((Invoke-RestMethod -uri 'https://api.github.com/repos/microsoft/winget-cli/releases/latest').assets | Where-Object { $_.browser_download_url.EndsWith('msixbundle') } | Select-Object -First 1).browser_download_url
                Invoke-WebRequest $WinGetUrl -OutFile "$WinGetPath/build/Microsoft.DesktopAppInstaller.zip" -UseBasicParsing
                Expand-Archive -Path "$WinGetPath/build/Microsoft.DesktopAppInstaller.zip" -DestinationPath "$WinGetPath/build/Microsoft.DesktopAppInstaller"
                Rename-Item -Path "$WinGetPath/build/Microsoft.DesktopAppInstaller/AppInstaller_x64.msix" -NewName "AppInstaller_x64.zip"
                Expand-Archive -Path "$WinGetPath/build/Microsoft.DesktopAppInstaller/AppInstaller_x64.zip" -DestinationPath "$WinGetPath/build/WinGet" -Force
    
                Remove-Item -Path "$WinGetPath/WinGet" -Recurse -Force -ErrorAction SilentlyContinue
                Move-Item -Path "$WinGetPath/build/WinGet" -Destination $WinGetPath -PassThru
                Remove-Item -Path "$WinGetPath/build" -Recurse -Force -ErrorAction SilentlyContinue
                # cmd /c setx /M PATH "%PATH%;$ENV:PROGRAMFILES\WinGet"

                # give the Users group full control over the WinGet directory
                $acl = Get-Acl -Path $WinGetPath
                $rule = New-Object System.Security.AccessControl.FileSystemAccessRule("Authenticated Users", "FullControl", "ContainerInherit,ObjectInherit", "None", "Allow")

                $acl.SetAccessRule($rule)
                Set-Acl -Path $WinGetPath -AclObject $acl
            }

            if (-not (Test-Path "$WinGetPath\WinGet\winget.exe" -PathType Leaf)) {
                Write-Output "Downloading WinGet and dependencies..."
                BuildWinGet
            }
            else {
                Write-Output "WinGet is already installed. Checking for updates..."

                $WinGetVersion = (Invoke-RestMethod -uri 'https://api.github.com/repos/microsoft/winget-cli/releases/latest').tag_name
                $WinGetInstalledVersion = & "$WinGetPath\WinGet\winget.exe" --version

                if ($WinGetInstalledVersion -eq $WinGetVersion) {
                    Write-Output "WinGet is already at the latest version ($WinGetVersion). Skipping installation."
                    return
                }
                Write-Output "Updating WinGet to version $WinGetVersion..."
                Remove-Item -Path $WinGetPath -Recurse -Force -ErrorAction Stop
                BuildWinGet

            }
        }

        try {
            InstallWinGet
        } 
        catch {
            if ($_.Exception.Message -match "no valid module file was found") {
                Write-Host "[INFO] WinGet directory found but module not loaded. Removing and retrying..."
                Remove-Item -Path $WinGetPath -Recurse -Force
                InstallWinGet
            }
            else {
                throw $_
            }
        }

        $WinGetPath = "$WinGetPath\WinGet\winget.exe"

        $spinners = '\', '/', '|', '-' | ForEach-Object { "^\s*\$_\s*`$" } | Join-String -Separator '|'
        $progressBars = '█', '▒' | ForEach-Object { "^.+\$_.+`$" } | Join-String -Separator '|'
        $blankLine = '^\s*$'
        $skip = "$spinners|$progressBars|$blankLine"
        
        if (-not $Action) {
            if ($env:action -and $env:action -notlike "null") {
                $Action = $env:action
            }
            else {
                Write-Host "[Error] You must specify an action to take (Install, Uninstall or Upgrade)"
                exit 1
            }
        }

        if (-not $PackageId) {
            if ($env:packageId -and $env:packageId -notlike "null") {
                $PackageId = $env:packageId
            }
        }

        if (-not $PackageId -and -not $PackageIdSelect) {
            if ($env:packageIdSelect -and $env:packageIdSelect -notlike "null") {
                $PackageId = $env:packageIdSelect
            }
        }

        Write-Host "[INFO] Running WinGet with action: $Action"
        Write-Host "[INFO] Running WinGet with Package ID: $PackageId"

        if ($Action -like "Install") {
            if (-not $PackageId) {
                Write-Host "[ERROR] You must specify a Package ID to install"
                exit 1
            }

            if ($PackageId -like "*`,*") {
                $packageIds = $PackageId -split "," | ForEach-Object { $_.Trim() }

                foreach ($tmpPackageId in $packageIds) {
                    & $WinGetPath install --id $tmpPackageId $installArgs | Where-Object { $_ -notmatch $skip }
                }
            }
            else {
                & $WinGetPath install --id $PackageId $installArgs | Where-Object { $_ -notmatch $skip }
            }
        }

        if ($Action -like "Uninstall") {
            if (-not $PackageId) {
                Write-Host "[ERROR] You must specify a Package ID to uninstall"
                exit 1
            }

            if ($PackageId -like "*`,*") {
                $packageIds = $PackageId -split "," | ForEach-Object { $_.Trim() }

                foreach ($tmpPackageId in $packageIds) {
                    Write-Host "[INFO] Uninstalling $tmpPackageId..."
                    & $WinGetPath uninstall --id $tmpPackageId $uninstallArgs | Where-Object { $_ -notmatch $skip }
                }
            }
            else {
                Write-Host "[INFO] Uninstalling $PackageId..."
                & $WinGetPath uninstall --id $PackageId $uninstallArgs | Where-Object { $_ -notmatch $skip }
            }
        }

        if ($Action -like "Upgrade" -OR $Action -like "Update") {
            if (-not $PackageId) {
                & $WinGetPath upgrade --all $installArgs | Where-Object { $_ -notmatch $skip }
            }
            else {
                if ($PackageId -like "*`,*") {
                    $packageIds = $PackageId -split "," | ForEach-Object { $_.Trim() }
    
                    foreach ($tmpPackageId in $packageIds) {
                        Write-Host "[INFO] Updating $tmpPackageId..."
                        & $WinGetPath upgrade --id $tmpPackageId $installArgs | Where-Object { $_ -notmatch $skip }
                    }
                }
                else {
                    Write-Host "[INFO] Updating $PackageId..."
                    & $WinGetPath upgrade --id $PackageId $installArgs | Where-Object { $_ -notmatch $skip }
                }
            }
        }
        
        Write-Host "[INFO] Completed running WinGet."
        exit 0
    }
    catch {
        Write-Host "[ERROR] Error running WinGet."
        Write-Host "[ERROR] Error occurred at line number: $($_.InvocationInfo.ScriptLineNumber)"
        Write-Error $_.exception
        exit 1
    }
}
end {
    Stop-Transcript
}