Initialize-Winget.ps1

<#PSScriptInfo
 
.VERSION 1.2
 
.GUID df32c5bb-3f4a-46d3-9304-96addbf693f4
 
.AUTHOR Jonathan Pitre
 
.TAGS Winget Intune Autopilot PowerShell Automation
 
.RELEASENOTES
 
1.2 - 2025-05-30
- Fixed "Find-ADTWinGetPackage -Id Microsoft.AppInstaller -Count 1 -Source winget"
- Improved logging and error handling.
 
1.1 - 2025-05-30
- Fixed "Failed to execute Winget command: Cannot validate argument on parameter 'Id'"
- Improved logging and error handling.
 
1.0 - 2025-05-28
- Initial release.
#>


<#
.SYNOPSIS
    Initializes and verifies WinGet installation, attempting repairs if necessary. Fix Winget issues on Autopilot.
 
.DESCRIPTION
    This script ensures WinGet is properly installed and functional. It will:
    1. Verify WinGet can find packages using the 'Microsoft.WinGet.Client' module.
    2. Attempt to repair WinGet if package search fails.
    3. Install WinGet and its dependencies if repair fails.
    4. Locate and verify the winget.exe path.
 
    The script also ensures that necessary helper modules ('Microsoft.WinGet.Client', 'PSAppDeployToolkit.WinGet') are installed and imported.
 
.PARAMETER WinGetId
    The WinGet package ID to verify functionality (e.g., "Microsoft.AppInstaller"). This is used to test if WinGet is working properly by attempting to find this package.
 
.EXAMPLE
    Initialize-WinGet -WinGetId "Microsoft.AppInstaller"
    # Initializes WinGet and verifies it can find the Microsoft App Installer package.
 
.OUTPUTS
    The found WinGet package information if successful.
 
.NOTES
    This function requires and will attempt to install/update the following PowerShell modules from the PowerShell Gallery:
    - 'Microsoft.WinGet.Client': Provides cmdlets to interact with the WinGet service, allowing for searching, installing, and managing packages.
    - 'PSAppDeployToolkit.WinGet': A module likely used to integrate WinGet operations within the PowerShell App Deployment Toolkit framework or provide supplementary WinGet functionalities.
 
    Administrative privileges are generally required for installing/updating these modules and for WinGet repair/installation operations.
    An active internet connection is needed to download modules from the PowerShell Gallery and for WinGet to function.
#>


[CmdletBinding()]
param (
    [Parameter(Mandatory = $false)]
    [string]$WinGetId = 'Microsoft.AppInstaller',
    [Parameter(Mandatory = $false)]
    [string]$LogPath = "$env:ProgramData\Microsoft\IntuneManagementExtension\Logs",
    [Parameter(Mandatory = $false)]
    [string]$LogFile = 'WinGet_Initialization.log'
)

begin {
    $ProgressPreference = 'SilentlyContinue'

    # Create log directory if it doesn't exist
    if (-not (Test-Path -Path $LogPath)) {
        New-Item -Path $LogPath -ItemType Directory -Force | Out-Null
    }

    # Start transcript
    Start-Transcript -Path (Join-Path -Path $LogPath -ChildPath $LogFile) -Force -Append

    Write-Host 'Starting WinGet initialization process...' -ForegroundColor Cyan

    # Install required modules
    $requiredModules = @('Microsoft.WinGet.Client', 'PSAppDeployToolkit.WinGet')
    foreach ($module in $requiredModules) {
        if (-not (Get-Module -ListAvailable -Name $module -ErrorAction Stop)) {
            Write-Verbose "Installing required module: $module..."
            $null = Install-Module -Name $module -Force -Scope AllUsers -AllowClobber -Repository PSGallery -ErrorAction Stop
        } else {
            Write-Verbose "Updating module: $module"
            Update-Module -Name $module -ErrorAction SilentlyContinue
        }
        # PSAppDeployToolkit.WinGet gives an error when importing, so we'll suppress it.
        Import-Module -Name $module -Force -ErrorAction SilentlyContinue
    }
}

process {
    try {
        function Get-WinGetPath {
            # Get the latest winget.exe path
            try {
                $WinGet = Get-ChildItem -Path "$env:ProgramW6432\WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe\winget.exe" -ErrorAction Stop |
                    Sort-Object LastWriteTime -Descending |
                    Select-Object -First 1 -ExpandProperty FullName

                if (-not $WinGet) {
                    Write-Warning 'Failed to locate winget.exe.'
                }
            } catch {
                Write-Warning "Failed to locate winget.exe: $_"
            }
            return $WinGet
        }

        try {
            # Verify if WinGet path can be found
            $WinGet = Get-WinGetPath
            Write-Verbose "Attempting to find WinGet package: $WinGetId"
            # Try to find WinGet package
            $WinGetSearchResult = Find-ADTWinGetPackage -Id $WinGetId -Count 1 -Source winget

        } catch {
            Write-Warning "Failed to execute Winget command: $_"
            Write-Verbose 'Attempting to repair WinGet...'

            try {
                # Verify if WinGet path can be found
                $WinGet = Get-WinGetPath
                Write-Verbose 'Attempting to repair WinGet Package Manager.'
                # Try to repair WinGet first
                Repair-WinGetPackageManager -AllUsers -Force -Latest

                Write-Verbose "Verifying WinGet package after repair: $WinGetId"
                # Verify if repair worked by trying to find package again
                $WinGetSearchResult = Find-ADTWinGetPackage -Id $WinGetId -Count 1 -Source winget # Re-assign to update
            } catch {
                Write-Warning "Failed to repair WinGet: $_"
                Write-Verbose 'Attempting to install WinGet and dependencies...'

                try {
                    # Install WinGet and dependencies
                    $WingetInstallScriptDirectory = Join-Path -Path $env:ProgramW6432 -ChildPath 'WindowsPowerShell\Scripts'
                    Write-Verbose "Target directory for winget-install.ps1: $WingetInstallScriptDirectory"

                    # Ensure the target directory exists. New-Item -Force creates parent directories if needed.
                    if (-not (Test-Path -Path $WingetInstallScriptDirectory)) {
                        Write-Verbose "Ensuring WinGet installation script directory exists: $WingetInstallScriptDirectory"
                        New-Item -ItemType Directory -Path $WingetInstallScriptDirectory -Force -ErrorAction SilentlyContinue | Out-Null
                    }

                    # Save the script to the determined target directory.
                    # Using Save-Script instead of Install-Script to precisely control the installation path to ensure it's in the 64-bit Program Files.
                    $WingetInstallScript = Join-Path -Path $WingetInstallScriptDirectory -ChildPath 'winget-install.ps1'
                    Write-Verbose "Saving winget-install script to $WingetInstallScript"
                    Save-Script -Name winget-install -Path $WingetInstallScriptDirectory -Force

                    Write-Verbose "Executing WinGet installation script: $WingetInstallScript"
                    # Run the installation script with system context
                    Start-Process -FilePath "$($env:WINDIR)\SysNative\WindowsPowerShell\v1.0\powershell.exe" -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$WingetInstallScript`" -Force -ForceClose" -Wait -NoNewWindow

                    # Verify if installation worked
                    $WinGet = Get-WinGetPath
                    Write-Verbose "Verifying WinGet package after installation: $WinGetId"
                    # Try to find WinGet package
                    $WinGetSearchResult = Find-ADTWinGetPackage -Id $WinGetId -Count 1 -Source winget # Re-assign to update
                } catch {
                    Write-Warning "Failed to install WinGet: $_"
                    Write-Verbose 'Attempting to install WinGet and dependencies with alternate method...'

                    try {
                        Write-Verbose "Executing WinGet installation script (alternate method): $WingetInstallScript"
                        # Install WinGet and dependencies with alternate method
                        Start-Process -FilePath 'powershell.exe' -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$WingetInstallScript`" -Force -ForceClose -AlternateInstallMethod" -Wait -NoNewWindow

                        # Verify if installation worked
                        $WinGet = Get-WinGetPath
                        Write-Verbose "Verifying WinGet package after alternate installation: $WinGetId"
                        # Try to find WinGet package
                        $WinGetSearchResult = Find-ADTWinGetPackage -Id $WinGetId -Count 1 -Source winget # Re-assign to update
                    } catch {
                        throw "Failed to install WinGet: $_"
                    }
                }
            }
        }

        $WinGetVersion = (& $WinGet --version).Trim('v')
        Write-Host "WinGet version: $WinGetVersion" -ForegroundColor Green
        Write-Host "Winget.exe is installed at: $WinGet" -ForegroundColor Green
        Write-Host "Found WinGet package: $WinGetId" -ForegroundColor Green
    } catch {
        throw "An error occurred during WinGet initialization: $_"
    }
}

end {
    # Stop transcript
    Stop-Transcript
}