microsoft / winget-cli

WinGet is the Windows Package Manager. This project includes a CLI (Command Line Interface), PowerShell modules, and a COM (Component Object Model) API (Application Programming Interface).
https://learn.microsoft.com/windows/package-manager/
MIT License
22.84k stars 1.42k forks source link

Receive `Specified method is not supported.` for multiple module cmdlets under SYSTEM context. #3935

Open mjr4077au opened 8 months ago

mjr4077au commented 8 months ago

Brief description of your issue

It's been exciting to see 1.6.3133.0 of the PowerShell module, its really starting to come together.

While the main cmdlets like Get-WinGetPackage, Install-WinGetPackage and Uninstall-WinGetPackage are working well, other ones that I require such as Get-WinGetVersion and Repair-WinGetPackageManager are not, and they're crucial as during a Windows Autopilot sequence, I need to ensure an appropriate version of WinGet is available vs. whatever the system ships with.

Steps to reproduce

  1. Open PowerShell 7.x as SYSTEM
  2. Call Get-WinGetVersion and observe error occurs.

Expected behavior

C:\Windows\System32>pwsh.exe -ExecutionPolicy Bypass -MTA PowerShell 7.4.0 PS C:\Windows\System32> whoami nt authority\system PS C:\Windows\System32> Get-WinGetVersion v1.6.3133 PS C:\Windows\System32>

Actual behavior

C:\Windows\System32>pwsh.exe -ExecutionPolicy Bypass -MTA PowerShell 7.4.0 PS C:\Windows\System32> whoami nt authority\system PS C:\Windows\System32> Get-WinGetVersion Get-WinGetVersion: Specified method is not supported. PS C:\Windows\System32>

Environment

Windows Package Manager v1.6.3133
Copyright (c) Microsoft Corporation. All rights reserved.

Windows: Windows.Desktop v10.0.22621.2715
System Architecture: X64

Winget Directories
------------------------------------------------------------------------------------------------------
Logs                               %TEMP%\WinGet\defaultState
User Settings                      %LOCALAPPDATA%\Microsoft\WinGet\Settings\defaultState\settings.json
Portable Links Directory (User)    %LOCALAPPDATA%\Microsoft\WinGet\Links
Portable Links Directory (Machine) C:\Program Files\WinGet\Links
Portable Package Root (User)       %LOCALAPPDATA%\Microsoft\WinGet\Packages
Portable Package Root              C:\Program Files\WinGet\Packages
Portable Package Root (x86)        C:\Program Files (x86)\WinGet\Packages
Installer Downloads                %USERPROFILE%\Downloads

Links
---------------------------------------------------------------------------
Privacy Statement   https://aka.ms/winget-privacy
License Agreement   https://aka.ms/winget-license
Third Party Notices https://aka.ms/winget-3rdPartyNotice
Homepage            https://aka.ms/winget
Windows Store Terms https://www.microsoft.com/en-us/storedocs/terms-of-sale

Admin Setting                             State
--------------------------------------------------
LocalManifestFiles                        Disabled
BypassCertificatePinningForMicrosoftStore Disabled
InstallerHashOverride                     Disabled
LocalArchiveMalwareScanOverride           Disabled
Trenly commented 8 months ago
mjr4077au commented 8 months ago

Was something missed in the PR?

Chris-Sanford commented 8 months ago

+1 on this. Given the instability/inconsistency I've experienced across our fleet when leveraging WinGet as SYSTEM, cmdlets like Get-WinGetVersion and Repair-WinGetPackageManager are important for supporting successful deployments.

Trenly commented 5 months ago

@mjr4077au - Is this still occurring with the 1.7.10651 release of the modules?

mjr4077au commented 5 months ago

Unfortunately so, for both Windows PowerShell and PowerShell 7: image The repair command is essential because Windows images often ship with ludicrously out of date WinGet versions.

mjr4077au commented 5 months ago

Windows PowerShell support is very inconsistent as well. Take Get-WinGetPackage, under SYSTEM it throws saying Windows PowerShell is not supported, however the cmdlet works fine under non-SYSTEM contexts: image

Trenly commented 5 months ago

Thanks for confirming

Chris-Sanford commented 3 months ago

Has there been any development or movement on this issue? We're now relying on our private WinGet source to be available under the SYSTEM context and are unable to configure said source using Add-WinGetSource as the same Specified method is not supported error is thrown (behavior observed in version 1.7.11132 of Microsoft.WinGet.Client PS Module), and of course the winget cli is completely unavailable under the SYSTEM context.

Is there any alternative way we could go about configuring WinGet sources under the SYSTEM context without relying on a currently-broken Add-WinGetSource?

mjr4077au commented 3 months ago

I'm currently using WinGet via the CLI while running as SYSTEM presently, it's just a cumbersome (albeit reliable) solution due to all the text parsing I have to do from the app's stdout, etc, when there's a shiny module I should be able to use.

Chris-Sanford commented 3 months ago

I'm currently using WinGet via the CLI while running as SYSTEM presently, it's just a cumbersome (albeit reliable) solution due to all the text parsing I have to do from the app's stdout, etc, when there's a shiny module I should be able to use.

How exactly are you accomplishing this?

mjr4077au commented 3 months ago

I can't provide my code as its considered proprietary to our company, and it depends on our own logging module and other internal code but the main guts to get things going, updated, working, and a valid path to winget.exe is:

#---------------------------------------------------------------------------
#
# Installs the latest Visual Studio Runtime dependency.
#
#---------------------------------------------------------------------------

Microsoft.PowerShell.Management\Set-Item -LiteralPath Function:Install-VisualStudioRuntimeDependency -Options Constant -Value {
    # Set required variables for install operation.
    $pkgArch = @('x86','x64')[[System.Environment]::Is64BitProcess]
    $pkgName = "Microsoft Visual C++ 2015-2022 Redistributable ($pkgArch)"
    $uriPath = "https://aka.ms/vs/17/release/vc_redist.$pkgArch.exe"
    TMLSTL.Logging\Write-LogEntry -Message "Preparing $pkgName dependency, please wait..."

    # Define arguments for installation.
    $spParams = @{
        FilePath = "$Env:TEMP\$(Microsoft.PowerShell.Utility\Get-Random).exe"
        ArgumentList = "/install", "/quiet", "/norestart", "/log $(TMLSTL.Logging\Out-LogFilePath -Identifier MSVCRT)"
    }

    # Download and extract installer.
    TMLSTL.Logging\Write-LogEntry -Message "Downloading $pkgName, please wait..."
    TMLSTL.Utilities\Invoke-CommandWithRetries -Command Microsoft.PowerShell.Utility\Invoke-WebRequest -UseBasicParsing -Uri $uriPath -OutFile $spParams.FilePath -Verbose 4>&1 | TMLSTL.Logging\Send-VerboseRecordsToLog

    # Invoke installer.
    TMLSTL.Logging\Write-LogEntry -Message "Installing $pkgName, please wait..."
    TMLSTL.Logging\Update-ExitCode -Value (Microsoft.PowerShell.Management\Start-Process @spParams -Wait -PassThru).ExitCode
}

#---------------------------------------------------------------------------
#
# Pre-provisions the latest WinGet binaries.
#
#---------------------------------------------------------------------------

Microsoft.PowerShell.Management\Set-Item -LiteralPath Function:Install-DesktopAppInstallerDependency -Options Constant -Value {
    # Update WinGet to the latest version. Don't rely in 3rd party store API services for this.
    # https://learn.microsoft.com/en-us/windows/package-manager/winget/#install-winget-on-windows-sandbox
    TMLSTL.Logging\Write-LogEntry -Message "Updating $(($pkgName = "Microsoft.DesktopAppInstaller")) dependency, please wait..."

    # Define installation file info.
    $packages = @(
        @{
            Name = 'C++ Desktop Bridge Runtime dependency'
            Uri = ($uri = [System.Uri]'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx')
            FilePath = "$($env:TEMP)\$($uri.Segments[-1])"
        }
        @{
            Name = 'Windows UI Library dependency'
            Uri = ($uri = [System.Uri]'https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.x64.appx')
            FilePath = "$($env:TEMP)\$($uri.Segments[-1])"
        }
        @{
            Name = 'latest WinGet msixbundle'
            Uri = ($uri = TMLSTL.Utilities\Get-RedirectedUri -Uri 'https://aka.ms/getwinget')
            FilePath = "$($env:TEMP)\$($uri.Segments[-1])"
        }
    )

    # Download all packages.
    foreach ($package in $packages)
    {
        TMLSTL.Logging\Write-LogEntry -Message "Downloading $($package.Name), please wait..."
        TMLSTL.Utilities\Invoke-CommandWithRetries -Command Microsoft.PowerShell.Utility\Invoke-WebRequest -UseBasicParsing -Uri $package.Uri -OutFile $package.FilePath -Verbose 4>&1 | TMLSTL.Logging\Send-VerboseRecordsToLog
    }

    # Pre-provision package in the system.
    $aappParams = @{
        Online = $true
        SkipLicense = $true
        PackagePath = $packages[(-1)].FilePath
        DependencyPackagePath = $packages[(0)..($packages.Count-2)].FilePath
        LogPath = TMLSTL.Logging\Out-LogFilePath -Identifier Dism
    }
    TMLSTL.Logging\Write-LogEntry -Message "Pre-provisioning $pkgName $($packages[-1].Uri.Segments[-2].Trim('/')), please wait..."
    Dism\Add-AppxProvisionedPackage @aappParams
}

#---------------------------------------------------------------------------
#
# Gets the correct, fully-qualified path to winget.exe.
#
#---------------------------------------------------------------------------

Microsoft.PowerShell.Management\Set-Item -LiteralPath Function:Out-WinGetPath -Options Constant -Value {
    # Get the path to WinGet, with special handling if we're running as SYSTEM or not.
    $wingetAppx = Appx\Get-AppxPackage -Name Microsoft.DesktopAppInstaller -AllUsers:$systemUser
    $wingetPath = "$($wingetAppx | Microsoft.PowerShell.Utility\Sort-Object -Property Version | Microsoft.PowerShell.Utility\Select-Object -ExpandProperty InstallLocation -Last 1)\winget.exe"
    $wingetProv = $null

    # Test whether we have any output from WinGet.exe. If this is null, it typically means the appropriate MSVC++ runtime is not installed.
    if (!($wingetOutput = & $wingetPath))
    {
        # We can only reliably update if we're elevated.
        if (!(TMLSTL.Utilities\Test-ElevatedSession))
        {
            throw "The installed version of WinGet was unable to run. Please ensure the latest Visual Studio 2015-2022 Runtime is installed and try again."
        }

        # Install the missing dependency.
        Install-VisualStudioRuntimeDependency

        # Re-check the output, ensuring it's not null.
        if (!($wingetOutput = & $wingetPath))
        {
            throw "The installed version of WinGet was unable to run. This is possibly related to the Visual Studio 2015-2022 Runtime."
        }
    }

    # Ensure winget.exe is above the minimum version.
    if ([System.Version]($wingetVer = ($wingetOutput | Microsoft.PowerShell.Utility\Select-Object -First 1) -replace '^.+\sv') -lt $wingetMinimum)
    {
        # We can only reliably update if we're elevated.
        if ($wingetProv -or !(TMLSTL.Utilities\Test-ElevatedSession))
        {
            throw "The installed WinGet version of $wingetVer is less than $wingetMinimum. Please update Microsoft.DesktopAppInstaller and try again."
        }

        # Install the missing dependency and reset variables.
        $wingetProv = Install-DesktopAppInstallerDependency
        $wingetAppx = Appx\Get-AppxPackage -Name Microsoft.DesktopAppInstaller -AllUsers:$systemUser
        $wingetPath = "$($wingetAppx | Microsoft.PowerShell.Utility\Sort-Object -Property Version | Microsoft.PowerShell.Utility\Select-Object -ExpandProperty InstallLocation -Last 1)\winget.exe"

        # Ensure winget.exe is above the minimum version.
        if ([System.Version]($wingetVer = (($wingetOutput = & $wingetPath) | Microsoft.PowerShell.Utility\Select-Object -First 1) -replace '^.+\sv') -lt $wingetMinimum)
        {
            throw "The installed WinGet version of $wingetVer is less than $wingetMinimum. Please check the DISM pre-provisioning logs and try again."
        }

        # Reset WinGet sources after updating. Helps with a corner-case issue discovered.
        TMLSTL.Logging\Write-LogEntry -Message "Resetting all WinGet sources following update, please wait..."
        if (!($wgSrcRes = & $wingetPath source reset --force 2>&1).Equals('Resetting all sources...Done'))
        {
            TMLSTL.Logging\Write-LogEntry -Message "An issue occurred while resetting WinGet sources: $($wgSrcRes.TrimEnd('.')). Continuing with operation." -Warning
        }
    }

    # Return tested path to the caller.
    TMLSTL.Logging\Write-LogEntry -Message "Using WinGet path: $wingetPath"
    return $wingetPath
}
SpecterShell commented 3 months ago

Just modified the code to make it run under SYSTEM context.

image

Unfortunately there are some compatibility issues with Windows PowerShell, so the module can only run in PowerShell Core under SYSTEM context. Besides, the user-scope packages and MSIX/APPX installers won't work.

mjr4077au commented 3 months ago

That's awesome! Will you be submitting a PR for that? Interesting that there's still Windows PowerShell 5.1 issues as it does work alright outside of the SYSTEM context from what I've seen: https://github.com/microsoft/winget-cli/issues/3935#issuecomment-1981996325

SpecterShell commented 3 months ago

Related changes are in https://github.com/SpecterShell/winget-cli/commit/8c024e4dbbed54216cd69632c068b7316c5837e4 and the build artifact is available in https://github.com/SpecterShell/Dumplings/actions/runs/9094089001. To use it,

  1. Get a portable copy of WinGet by using this script: https://github.com/SpecterShell/Dumplings/blob/main/Utilities/InstallWinGetPortable.ps1. After downloading, copy the files to somewhere, say C:\WinGet.
  2. Download the module files and import it manually
  3. Set the environmental variable $Env:CUSTOMWINGET='C:\WinGet' in PowerShell
  4. Now it should work.

EDIT: Weird. The winget.exe in C:\Program Files\WindowsApps didn't work when I was making these changes, but it worked again when I tried it just now. Probably you just need to specify $Env:CUSTOMWINGET = 'C:\Program Files\WindowsApps\Microsoft.DesktopAppInstaller_1.23.1133.0_x64__8wekyb3d8bbwe\'.

Will you be submitting a PR for that?

Probably not. This is more like a hack as it just simply removes the limitations of running WinGet in SYSTEM context, but didn't address any issues behind like the Windows PowerShell compatibility issues and how to use the module without using another copy of WinGet.

mjr4077au commented 3 months ago

Just saw this amongst your diff:

            if (Utilities.ExecutingAsSystem)
            {
                throw new NotSupportedException();
            }

I guess I'll keep using my own solution for now as its working well, but hopefully this situation gets rectified as you've demonstrated there's no technical reason for it not to work and support is required in the ConfigMgr/Intune space.