Category: Powershell

winget: new Windows package management
article #1504, updated 40 days ago

It’s built into most OEM installs of Windows 10 and 11, and can often be installed. On server builds it’s touch and go.

To see if you have it, try winget list from CMD or Powershell.

One good way to test it, is to install Microsoft .NET framework (SDK) 6, thus, from administrative Powershell:

winget install --id Microsoft.DotNet.Runtime.6 --silent --accept-source-agreements

I learned just now that if you add other seemingly valuable options to the one above, e.g., --scope machine, at least while running as SYSTEM, it will fail citing package not found. So you’ll want to test carefully.

Here’s one proven just now for 7zip (there’s a “search” option in winget to get the ID):

winget install --exact --id 7zip.7zip --accept-package-agreements --silent --scope machine

Here’s one for Google Chrome, needs a bit of extra:

winget.exe install --exact --id Google.Chrome --silent --accept-package-agreements --accept-source-agreements --scope machine

If you do want to use it from the SYSTEM account, in scripting, it gets interesting. You’ll want to first run the below, and then winget will run as expected.

# Function to find the path to winget.exe
function Find-WinGet-Path {
    # Get the WinGet path (for use when running in SYSTEM context).
    $WinGetPathToResolve = Join-Path -Path $ENV:ProgramFiles -ChildPath 'WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe'
    $ResolveWinGetPath = Resolve-Path -Path $WinGetPathToResolve | Sort-Object {
        [version]($_.Path -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1')
    }
    if ($ResolveWinGetPath) {
        # If we have multiple versions - use the latest.
        $WinGetPath = $ResolveWinGetPath[-1].Path
    }

    # Get the User-Context WinGet exe location.
    $WinGetExePath = Get-Command -Name winget.exe -CommandType Application -ErrorAction SilentlyContinue

    # Select the correct WinGet exe
    if (Test-Path -Path (Join-Path $WinGetPath 'winget.exe')) {
        # Running in SYSTEM-Context.
        $WinGet = Join-Path $WinGetPath 'winget.exe'
    } elseif ($WinGetExePath) {
        # Get User-Context if SYSTEM-Context not found.
        $WinGet = $WinGetExePath.Path
    } else {
        Write-Output 'WinGet not Found!'
        Stop-Transcript
        exit 1
    }

    # Return WinGet path
    return ($WinGet -replace '\winget.exe','')
}

Function Add-PathVariable {
    param (
        [string]$addPath
    )
    if (Test-Path $addPath){
        $regexAddPath = [regex]::Escape($addPath)
        $arrPath = $env:Path -split ';' | Where-Object {$_ -notMatch 
"^$regexAddPath\\?"}
        $env:Path = ($arrPath + $addPath) -join ';'
    } else {
        Throw "'$addPath' is not a valid path."
    }
}

Add-PathVariable (Find-Winget-Path)

Installing is even more interesting. There is a module in PSGallery called winget-install which works well in console, but not scripting. Below is one that works well in scripting, though it cannot be pasted interactively:

<# Winget-Install.ps1

Source for Some Functions: https://github.com/homotechsual/ninjaget/blob/dev/PS/NinjaGet.Functions.Installation.ps1

2023-11-22 - 0.0.2 - assembled and modified by David Szpunar - Initial version

DOWNLOAD: https://discord.com/channels/676451788395642880/1232432179141808228

2024-04-23 - 0.1.0 - Simplified and Modified by GraphicHealer - Initial Version

DESCRIPTION: Installs or updates WinGet.exe to the latest version. Intended to run as SYSTEM.
        Use '-Force' to ignore version check.
-----
LICENSE: The MIT License (MIT)

Original Copyright 2023 Mikey O'Toole, modified by David Szpunar, Simplified and Modified by GraphicHealer.
#>

# Add -Force switch to force install
param(
    [switch]$Force = $false
)

# Start Logging
Start-Transcript -Path ".\winget-install.log" -IncludeInvocationHeader -Append -Force

# Setup Error Codes
$Script:ExitCode = 0

# Function to get OS Information
function Get-OSInfo {
    try {
        # Get registry values
        $registryValues = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion'
        $releaseIdValue = $registryValues.ReleaseId
        $displayVersionValue = $registryValues.DisplayVersion
        $nameValue = $registryValues.ProductName
        $editionIdValue = $registryValues.EditionId

        # Strip out "Server" from the $editionIdValue if it exists
        $editionIdValue = $editionIdValue -replace 'Server', ''

        # Get OS details using Get-CimInstance because the registry key for Name is not always correct with Windows 11
        $osDetails = Get-CimInstance -ClassName Win32_OperatingSystem
        $nameValue = $osDetails.Caption

        # Get architecture details of the OS (not the processor)
        # Get only the numbers
        $architecture = ($osDetails.OSArchitecture -replace '[^\d]').Trim()

        # If 32-bit or 64-bit replace with x32 and x64
        if ($architecture -eq '32') {
            $architecture = 'x32'
        } elseif ($architecture -eq '64') {
            $architecture = 'x64'
        }

        # Get OS version details (as version object)
        $versionValue = [System.Environment]::OSVersion.Version

        # Determine product type
        # Reference: https://learn.microsoft.com/en-us/dotnet/api/microsoft.powershell.commands.producttype?view=powershellsdk-1.1.0
        if ($osDetails.ProductType -eq 1) {
            $typeValue = 'Workstation'
        } elseif ($osDetails.ProductType -eq 2 -or $osDetails.ProductType -eq 3) {
            $typeValue = 'Server'
        } else {
            $typeValue = 'Unknown'
        }

        # Extract numerical value from Name
        $numericVersion = ($nameValue -replace '[^\d]').Trim()

        # Create and return custom object with the required properties
        $result = [PSCustomObject]@{
            ReleaseId      = $releaseIdValue
            DisplayVersion = $displayVersionValue
            Name           = $nameValue
            Type           = $typeValue
            NumericVersion = $numericVersion
            EditionId      = $editionIdValue
            Version        = $versionValue
            Architecture   = $architecture
        }

        return $result
    } catch {
        Write-Error "Unable to get OS version details.`nError: $_"
        exit 1
    }
}

# Function to Install WinGet (modified)
function Install-WinGet {
    # Install VCPPRedist Requirements
    $Visual2019 = 'Microsoft Visual C++ 2015-2019 Redistributable*'
    $Visual2022 = 'Microsoft Visual C++ 2015-2022 Redistributable*'
    $VCPPInstalled = Get-Item @('HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') | Where-Object {
        $_.GetValue('DisplayName') -like $Visual2019 -or $_.GetValue('DisplayName') -like $Visual2022
    }
    if ([System.Environment]::Is64BitOperatingSystem) {
        $OSArch = 'x64'
    } else {
        $OSArch = 'x86'
    }
    if (!($VCPPInstalled)) {
        Write-Output 'VCPPRedist Requirements Not Installed. Installing...'

        $VCPPRedistURL = ('https://aka.ms/vs/17/release/vc_redist.{0}.exe' -f $OSArch)
        $VCPPRedistFileName = [Uri]$VCPPRedistURL | Select-Object -ExpandProperty Segments | Select-Object -Last 1
        $WebClient = New-Object System.Net.WebClient
        $VCPPRedistDownloadPath = "$ENV:SystemRoot\Temp\VCPRedist"
        if (!(Test-Path -Path $VCPPRedistDownloadPath)) {
            $null = New-Item -Path $VCPPRedistDownloadPath -ItemType Directory -Force
        }
        $VCPPRedistDownloadFile = "$VCPPRedistDownloadPath\$VCPPRedistFileName"
        $WebClient.DownloadFile($VCPPRedistURL, $VCPPRedistDownloadFile)
        try {
            Start-Process -FilePath $VCPPRedistDownloadFile -ArgumentList '/quiet', '/norestart' -Wait -ErrorAction Stop | Out-Null
            Write-Output 'VCPPRedist Requirements Installed.'
        } catch {
            Write-Output 'Failed to install VCPPRedist Requirements!'
            $Script:ExitCode = 1
            return
        } finally {
            Remove-Item -Path $VCPPRedistDownloadPath -Recurse -Force -ErrorAction SilentlyContinue
        }
    } else {
        Write-Output 'VCPPRedist Requirements Installed.'
    }

    # Find Latest Download
    $LatestRelease = (Invoke-RestMethod -Uri 'https://api.github.com/repos/microsoft/winget-cli/releases/latest' -Method Get)
    $LatestAsset = ($LatestRelease).assets | Where-Object { $_.name.EndsWith('.msixbundle') }
    [version]$LatestVersion = $LatestRelease.tag_name.TrimStart('v')
    if ((Find-WinGet)) {
        [version]$CurrentVersion = (& (Find-WinGet) '-v').TrimStart('v')
    } else {
        [version]$CurrentVersion = 0
    }

    # Check if WinGet is Updated
    if (!($CurrentVersion -lt $LatestVersion) -and !$Force) {
        # Exit
        Write-Output 'WinGet is up to date. Exiting.'
        return
    }

    # Installing WinGet
    Write-Output 'WinGet not installed or out of date. Installing/updating...'

    # Download WinGet to Temp
    $WebClient = New-Object System.Net.WebClient
    $WinGetDownloadPath = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath $LatestAsset.name
    Write-Output "Downloading WinGet to $WinGetDownloadPath..."
    $WebClient.DownloadFile($LatestAsset.browser_download_url, $WinGetDownloadPath)

    # Download WinGet Source to Temp
    $WinGetSourceDownloadPath = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath 'source.msix'
    Write-Output "Downloading WinGet to $WinGetSourceDownloadPath..."
    Invoke-WebRequest -Uri 'https://cdn.winget.microsoft.com/cache/source.msix' -OutFile $WinGetSourceDownloadPath

    $WinArch = (Get-OSInfo).Architecture

    # Download VCLibs
    $VCLibs_Path = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath "Microsoft.VCLibs.${WinArch}.14.00.Desktop.appx"
    $VCLibs_Url = "https://aka.ms/Microsoft.VCLibs.${WinArch}.14.00.Desktop.appx"
    Write-Output 'Downloading VCLibs...'
    Write-Debug "Downloading VCLibs from $VCLibs_Url to $VCLibs_Path`n`n"
    Invoke-WebRequest -Uri $VCLibs_Url -OutFile $VCLibs_Path

    # Download UI.Xaml
    $UIXaml_Path = Join-Path -Path "$ENV:SystemRoot\Temp" -ChildPath "Microsoft.UI.Xaml.2.8.${WinArch}.appx"
    $UIXaml_Url = "https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.6/Microsoft.UI.Xaml.2.8.${WinArch}.appx"
    Write-Output 'Downloading UI.Xaml...'
    Write-Debug "Downloading UI.Xaml from $UIXaml_Url to $UIXaml_Path`n"
    Invoke-WebRequest -Uri $UIXaml_Url -OutFile $UIXaml_Path

    # Install Downloaded WinGet
    try {
        Write-Output 'Installing WinGet...'
        Add-AppxProvisionedPackage -Online -PackagePath $WinGetDownloadPath -DependencyPackagePath $UIXaml_Path, $VCLibs_Path -SkipLicense -ErrorAction Stop
        Add-AppxProvisionedPackage -Online -PackagePath $WinGetSourceDownloadPath -SkipLicense -ErrorAction Stop
        Write-Output 'WinGet installed.'
    } catch {
        Write-Output 'Failed to install WinGet!'
        $Script:ExitCode = 1
    } finally {
        Remove-Item -Path $WinGetDownloadPath -Force -ErrorAction SilentlyContinue
    }

    return
}

# Function to Find the WinGet exe (Modified)
function Find-WinGet {
    # Get the WinGet path (for use when running in SYSTEM context).
    $WinGetPathToResolve = Join-Path -Path $ENV:ProgramFiles -ChildPath 'WindowsApps\Microsoft.DesktopAppInstaller_*_*__8wekyb3d8bbwe'
    $ResolveWinGetPath = Resolve-Path -Path $WinGetPathToResolve | Sort-Object {
        [version]($_.Path -replace '^[^\d]+_((\d+\.)*\d+)_.*', '$1')
    }
    if ($ResolveWinGetPath) {
        # If we have multiple versions - use the latest.
        $WinGetPath = $ResolveWinGetPath[-1].Path
    }

    # Get the User-Context WinGet exe location.
    $WinGetExePath = Get-Command -Name winget.exe -CommandType Application -ErrorAction SilentlyContinue

    # Select the correct WinGet exe
    if (Test-Path -Path (Join-Path $WinGetPath 'winget.exe')) {
        # Running in SYSTEM-Context.
        $WinGet = Join-Path $WinGetPath 'winget.exe'
    } elseif ($WinGetExePath) {
        # Get User-Context if SYSTEM-Context not found.
        $WinGet = $WinGetExePath.Path
    } else {
        Write-Output 'WinGet not Found!'
        Stop-Transcript
        exit 1
    }

    # Return WinGet Location
    return $WinGet
}

# Test internet connection
$MaxNet = 15
Write-Output 'Checking Internet Connection...'
for ($i = 0; ($i -lt $MaxNet) -and !$net; $i++) {
    $net = Test-Connection 1.1.1.1 -Count 1 -Delay 1 -Quiet
}
if (!$net) {
    Write-Output 'No Internet Detected. Cancelling Installation.'
    Stop-Transcript
    exit 1
}

Write-Output 'Internet Detected, Continuing Installation.'

# Test OS Compatibility
$osVersion = Get-OSInfo

# If it's a workstation, make sure it is Windows 10+
if ($osVersion.Type -eq 'Workstation' -and $osVersion.NumericVersion -lt 10) {
    Write-Error 'winget requires Windows 10 or later on workstations. Your version of Windows is not supported.'
    Stop-Transcript
    exit 0
}

# If it's a workstation with Windows 10, make sure it's version 1809 or greater
if ($osVersion.Type -eq 'Workstation' -and $osVersion.NumericVersion -eq 10 -and $osVersion.ReleaseId -lt 1809) {
    Write-Error 'winget requires Windows 10 version 1809 or later on workstations. Please update Windows to a compatible version.'
    Stop-Transcript
    exit 0
}

# Run the Install/Update
Install-WinGet

& (Find-WinGet) 'list' '--accept-source-agreements'

Stop-Transcript
exit $Script:ExitCode

Categories:      

==============

Reestablish Windows Domain Relationship in Powershell
article #1562, updated 381 days ago

If you can get to an administrative or system shell:

Reset-ComputerMachinePassword -Server DC.domain.local -Credential (Get-Credential)

Categories:      

==============

Install/Update All Microsoft 365 Powershell Modules (Not Exchange Online)
article #1526, updated 497 days ago

Try this:

@("MsOnline","ExchangeOnlineManagement","AzureAD","AzureRM","Az","Microsoft.Graph","MicrosoftTeams","Microsoft.Online.SharePoint.PowerShell","Microsoft.PowerApps.Administration.PowerShell","Microsoft.PowerApps.PowerShell","WhiteboardAdmin","O365CentralizedAddInDeployment","PnP.PowerShell","MicrosoftPowerBIMgmt")|%{if(!(get-module -listavailable -name $_)){install-module -name $_ -skippublishercheck -allowclobber -force}elseif((get-module -listavailable -name $_).version -lt (find-module $_).version){update-module -name $_ -force;Get-InstalledModule -name $_ -allversions| where {$_.version -lt (get-installedmodule -name $_.name).version} | Uninstall-Module -force}}

From www.tbone.se/2023/02/27/update-your-windows-11-with-some-powerful-one-liners/

Categories:      

==============

NIC speed in Powershell
article #1530, updated 559 days ago

To get the link speed of all NICs in Powershell:

Get-NetAdapter | select interfaceDescription, name, status, linkSpeed.

Categories:      

==============

Update All Installed Powershell Modules
article #1527, updated 569 days ago

It also removes old versions.

Get-InstalledModule |%{if((get-module -listavailable -name $_.name).version -lt (find-module $_.name).version){update-module -name $_.name -force;Get-InstalledModule -name $_.name -allversions| where {$_.version -lt (get-installedmodule -name $_.name).version} | Uninstall-Module -force}}

From www.tbone.se/2023/02/27/update-your-windows-11-with-some-powerful-one-liners/

Categories:      

==============

Update Windows via Powershell
article #1479, updated 721 days ago

This method uses Powershell module PsWindowsUpdate.

  1. Run this in administrative Powershell:
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Set-Executionpolicy RemoteSigned -Scope Process -Force
Install-PackageProvider -Name NuGet -Force -ErrorAction 'SilentlyContinue' > $null
Set-PSRepository -Name PSGallery -InstallationPolicy Trusted
If (Get-InstalledModule -Name PsWindowsUpdate -ErrorAction 'SilentlyContinue') {
	Update-Module -Name PSWindowsUpdate -Force
} Else {
	Install-Module -Name PSWindowsUpdate -Force
}
Import-Module PSWindowsUpdate
  1. Then check the list of available updates:
Get-WindowsUpdate
  1. The next step is to actually do the updates. There are good reasons and multiple methods to be careful. Alas, thus far, there does not appear to be a way to install updates a given number of days after release, e.g., 30, so as to give Microsoft time to respond to issues. Here is a glancing overview of what we do have:
  • Lots of firmware is being sent by Microsoft now, and some of this is more up-to-date than that available from the vendors. But there is risk in these, don’t forget. You may find that you want to install current Windows patches, but no drivers, firmware, services packs, feature packs, etc. To do this:
Install-WindowsUpdate -NotCategory "Drivers","Service Packs","FeaturePacks" -NotTitle "preview" -AcceptAll 

And to do it while ignoring reboot:

Install-WindowsUpdate -NotCategory "Drivers","Service Packs","FeaturePacks" -NotTitle "preview" -AcceptAll -IgnoreReboot

The -IgnoreReboot ignores all relevant reboot automata. -NotTitle "preview" omits all updates with the word “preview” in their name.

But sometimes, e.g. with a new PC install, we’ll want to install all updates and reboot automatically:

Install-WindowsUpdate -AcceptAll -AutoReboot
  • You may find that you want to omit granularly, e.g., specific build upgrades. If you found one marked KB1234567, you would install all and omit that one thus:
Install-WindowsUpdate -NotKBArticleID KB1234567 -AcceptAll
  • If you wanted to do that, and explicitly not reboot if indicated:
Install-WindowsUpdate -NotKBArticleID KB1234567 -AcceptAll -IgnoreReboot
  • If you had two KBs to omit:
Install-WindowsUpdate -AcceptAll -NotKBArticleID "KB1234567,KB7654321"
  • There are other noteworthy items. Lots of firmware is being sent by Microsoft now, and some of this is more up-to-date than that available from the vendor. But there is risk in firmware updates, don’t forget. Some of the items don’t have KBs, and there are two other command-line arguments to omit those, -NotTitle and -NotUpdateID.
  • And then there’s:
Reset-WUComponents
  • To get a full list of functions:
Get-Command -Module PSWindowsUpdate

Get-Help works for all of them.

Categories:      

==============

Set DNS in use via Powershell
article #1490, updated 751 days ago

  1. Open up an administrative Powershell. Run IPCONFIG /ALL. That will get you a list of active NICs. DNS in use, is set for each NIC if you have more than one.
  2. The name of each NIC has a prefix that has to be omitted. There are a number of prefixes which are common. For a simple wired NIC, it’s usually “Ethernet Adapter”; on many HPE servers, IPCONFIG /ALL will therefore show the second NIC as Ethernet adapter Embedded LOM 1 Port 2.
  3. So let’s say you have a LAN with three active DNS servers (10.11.12.13, 10.11.12.14, and 10.11.12.15), and you want your HPE server of the above description, with the first two NICs active, to use all of them. Here’s the Powershell commands:
Set-DnsClientServerAddress "Embedded LOM 1 Port 1" -ServerAddresses ("10.11.12.13","10.11.12.14","10.11.12.15")
Set-DnsClientServerAddress "Embedded LOM 1 Port 2" -ServerAddresses ("10.11.12.13","10.11.12.14","10.11.12.15")
  1. For a second example, let’s say we’re on a common workstation, and we want to change DNS from a static setting, to whatever DHCP will pass out:
Set-DnsClientServerAddress "Ethernet" -ResetServerAddresses

Categories:      

==============

Download URL Using Powershell
article #1439, updated 755 days ago

Longstanding, works well unless BITS is corrupt:

Start-BitsTransfer -Source $URL -Destination $Path

On 1809 and up:

curl.exe -O $URL

A pure Powershell method:

(New-Object System.Net.WebClient).DownloadFile($URL,$Path)

Categories:      

==============

Remove all user local admins from local Administrators group
article #1464, updated 1014 days ago

This removes all local admins from a machine’s Administrators group, except the built-in Administrator and “Domain Admins” if it’s on a domain.

$LocalDomain = $env:USERDOMAIN
$DomainAdmins = "$LocalDomain\Domain Admins"
$ComputerName = $env:COMPUTERNAME
$OEMAdministrator = "$ComputerName\Administrator"
Get-LocalGroupMember Administrators | ForEach-Object {
	$UserName = $_.Name
	"Found: $UserName"
	If (($UserName -ne $DomainAdmins) -and ($UserName -ne $OEMAdministrator)) {
		"Removing $UserName from local Administrators group."
		Remove-LocalGroupMember -Group Administrators -Member $UserName
		}
	""
}

Categories:      

==============

When Group Policy doesn't work, or works wrong
article #1458, updated 1052 days ago

If you see GPO policies get implemented and re-implemented even though the settings have been removed, or if it just doesn’t happen, try the following in administrative Powershell. These clear the GPO cache on the machine you’re looking at.

Remove-Item "$env:windir\system32\GroupPolicy" -Force -Recurse
Remove-Item "HKLM:\Software\Policies\Microsoft" -Force -Recurse
Remove-Item "HKCU:\Software\Policies\Microsoft" -Force -Recurse
Remove-Item "HKCU:\Software\Microsoft\Windows\CurrentVersion\Group Policy Objects" -Force -Recurse
Remove-Item "HKCU:\\Software\Microsoft\Windows\CurrentVersion\Policies" -Force -Recurse

Categories: