Category: Scripting

Install winget as Local System (for scripting)
article #1606, updated 40 days ago

This works for recent builds of 10 and 11. Server 2019 sometimes has issues.

<# 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

2024-10-11 - 0.1.1 - took out Ninja RMM item - JEB

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:      

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

Aaron J. Stevenson's Shared Script Library
article #1598, updated 92 days ago

This has some gems in it:

scripts.aaronjstevenson.com/

including a Dell Command Update script which reportedly takes care of all drivers and firmware on Dell non-servers in one swell foop!

Categories:      

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

Scheduling a Reboot via Script
article #1484, updated 678 days ago

The most reliable way I had for a long time, was using a scheduled task. But Powershell for this changes from Windows version to Windows version. Here’s a new method, it uses the time specification in the ‘shutdown’ command, to reboot the machine tomorrow at 3AM:

$tomorrow3AM = (Get-Date).AddHours(24)
$tomorrow3AM = $tomorrow3AM.AddHours( ($tomorrow3AM.Hour * -1) + 3 )
$tomorrow3AM = $tomorrow3AM.AddMinutes( $tomorrow3AM.Minute * -1 )
$tomorrow3AM = $tomorrow3AM.AddSeconds( $tomorrow3AM.Second * -1 )
$tomorrow3AM = $tomorrow3AM.AddMilliseconds( $tomorrow3AM.Millisecond * -1 )
$SecondsFromNow = ($tomorrow3AM - (Get-Date)).TotalSeconds
shutdown -f -r -t ([int] $SecondsFromNow)

Categories:      

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

Logoff all users from Windows
article #1478, updated 895 days ago

This will log off all users, whether console or RDP:

logoff console
quser /server:localhost | ForEach-Object {
		logoff $_.ID
		}

Categories:      

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

Scripting a Universal Popup
article #1436, updated 1221 days ago

Every scripting language which runs on Windows can call multiple methods of bringing up a popup to users. However, only this CMD method seems to do it for all users, and be callable universally:

msg * /time:9999 /v /w Put your message here!

Categories:      

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

Get Windows version info in Powershell
article #1338, updated 1856 days ago

Here’s a great place to start:

Get-CimInstance -Class Win32_OperatingSystem | ForEach-Object -MemberName Caption

This gets profoundly useful strings like “Microsoft Windows 10 Enterprise”. If you need the numeric version, the best so far has been:

(Get-ItemProperty -Path c:\windows\system32\hal.dll).VersionInfo.FileVersion

which, right now on this machine, gets us “10.0.18362.387 (WinBuild.160101.0800)”. And for the Windows 10 build:

(Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion' -Name ReleaseID -ErrorAction Stop).ReleaseID

which gets “1903”. All of these are fast, do not depend on systeminfo, and appear to be nice and reliable.

Categories:      

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

Silent Install of Adobe Reader DC
article #1318, updated 1905 days ago

First, download the EXE here:

https://get.adobe.com/reader/enterprise/

Then cause it to run thus:

AcroRdrDC1901220036_en_US.exe /sAll /rs /rps

A full list of command-line options can be had with /?.

Categories:      

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

Rick's Super-Duper CMD Code Accelerator
article #1306, updated 1968 days ago

This one is from the amazing Rick Boatright. I saw the ancestor of this thirty-plus years ago in Unix System V, had no idea it had gotten so useful in Microsoft-land. The gist of it is:

  • You have a batch file, and want to access something involving a UNC path, something like this: \\SERVER_NAME\share_name\dir1\dir2
  • Default logic often involves storage of current location into a variable, CD, resumption of previous, blah, blah, blah.
  • But we can do it in one command: pushd \\SERVER_NAME\share_name\dir1\dir2 This does multiple things:
  • First, it creates a temporary drive letter for the server and share name. It chooses an available drive letter.
  • Secondly, without any further ado, it changes the current working directory of the shell (of the script) to the very location you pointed at.
  • So, if you did the pushd above, and if Z: were available, your current working directory suddenly becomes: Z:\dir1\dir2
    where Z: is mapped to \\SERVER_NAME\share_name !!!
  • Then when you’re done with it, just put in popd, and Z: goes away and you’re back to the current working directory you had beforehand!

Categories:      

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

Close shared files on a Windows file server in Powershell
article #1288, updated 2025 days ago

Rather good to see:

https://docs.microsoft.com/en-us/powershell/module/smbshare/close-smbopenfile

Categories:      

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

Pin to Taskbar for All Users in Windows
article #1287, updated 2029 days ago

So we have a terminal server or other multi-user Windows machine, Windows 7/2008R2 or later. We want to pin one or more icons to the taskbar, for all users. We discover that this is not something extremely easy to do :-) We can, at least reasonably easily, set up the same taskbar icon set for all users, thusly:

  1. Log into the one machine, and set up the taskbar as you would like it to appear for all.
  2. Export the following registry key:
    [HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband]
    to a file named TaskBarPins.REG. Put it in a permanent folder outside of user space, e.g. C:\AutoSettings, it will be imported automatically at every login.
  3. Create SetTaskBarPins.VBS in C:\AutoSettings, containing the following text:
Option Explicit
On Error Resume Next
Dim objShell, ProgramFiles, sRegFile
Set objShell = CreateObject("WScript.Shell")
sRegFile = """C:\AutoSettings\SetTaskBarPins.REG"""
objShell.Run "Regedit.exe /s " & sRegFile, 0, True
Set objShell = Nothing
  1. Create a shortcut to C:\AutoSettings\SetTaskBarPins.VBS in C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp.

The next time any user logs into this machine, nothing will appear to have been changed. But when they log off and then log on after that, their taskbar will be the same as the one you exported.

Categories: