Skip to content

Instantly share code, notes, and snippets.

@alkampfergit
Last active April 2, 2024 19:02
Show Gist options
  • Save alkampfergit/2f662c07df0ca379c8e8e65e588c687b to your computer and use it in GitHub Desktop.
Save alkampfergit/2f662c07df0ca379c8e8e65e588c687b to your computer and use it in GitHub Desktop.
Upgrade with Winget being able to select list of software to skip
class Software {
[string]$Name
[string]$Id
[string]$Version
[string]$AvailableVersion
}
$upgradeResult = winget upgrade | Out-String
$lines = $upgradeResult.Split([Environment]::NewLine)
# Find the line that starts with Name, it contains the header
$fl = 0
while (-not $lines[$fl].StartsWith("Name"))
{
$fl++
}
# Line $i has the header, we can find char where we find ID and Version
$idStart = $lines[$fl].IndexOf("Id")
$versionStart = $lines[$fl].IndexOf("Version")
$availableStart = $lines[$fl].IndexOf("Available")
$sourceStart = $lines[$fl].IndexOf("Source")
# Now cycle in real package and split accordingly
$upgradeList = @()
For ($i = $fl + 1; $i -le $lines.Length; $i++)
{
$line = $lines[$i]
if ($line.Length -gt ($availableStart + 1) -and -not $line.StartsWith('-'))
{
$name = $line.Substring(0, $idStart).TrimEnd()
$id = $line.Substring($idStart, $versionStart - $idStart).TrimEnd()
$version = $line.Substring($versionStart, $availableStart - $versionStart).TrimEnd()
$available = $line.Substring($availableStart, $sourceStart - $availableStart).TrimEnd()
$software = [Software]::new()
$software.Name = $name;
$software.Id = $id;
$software.Version = $version
$software.AvailableVersion = $available;
$upgradeList += $software
}
}
$upgradeList | Format-Table
$toSkip = @(
'ArtifexSoftware.GhostScript',
'Microsoft.VC++2013Redist-x64',
'MongoDB.Server',
"Microsoft.VC++2015-2019Redist-x64",
"Microsoft.VC++2015-2019Redist-x86",
"Microsoft.VC++2013Redist-x86",
"Microsoft.OneDrive")
foreach ($package in $upgradeList)
{
if (-not ($toSkip -contains $package.Id))
{
Write-Host "Going to upgrade package $($package.id)"
& winget upgrade $package.id
}
else
{
Write-Host "Skipped upgrade to package $($package.id)"
}
}
@geraldodinukwe80
Copy link

geraldodinukwe80 commented Jan 3, 2023

Executing script retuned the error below. Could you please check where the problem lies. Thanks

Exception calling "Substring" with 2 argument(s): "The length cannot be less than 0 (zero).
Parameter name: length"
In C:\Users\Gerald\Desktop\HH\winget.ps1:34 characters:9

  • $name = $name.Substring(0, $idStart).TrimEnd()
  •    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
       + FullyQualifiedErrorId : ArgumentOutOfRangeException
    
    

Exception when calling "Substring" with 2 argument(s): "StartIndex must not be less than zero.
Parameter name: startIndex"
In C:\Users\Gerald\Desktop\HH\winget.ps1:35 characters:9

  • $id = $line.Substring($idStart, $versionStart - $idStart).Tri ...
  •    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
       + FullyQualifiedErrorId : ArgumentOutOfRangeException
    
    

Exception calling "Substring" with 2 argument(s): "The length cannot be less than 0 (zero).
Parameter name: length"
In C:\Users\Gerald\Desktop\HH\winget.ps1:36 characters:9

  • $version = $line.Substring($versionStart, $availableStart - $ ...
  •    + CategoryInfo : NotSpecified: (:) [], MethodInvocationException
       + FullyQualifiedErrorId : ArgumentOutOfRangeException
    

@cybersholt
Copy link

cybersholt commented Jan 8, 2023

I also get a similar error as @geraldodinukwe80. Also I am getting some encoding issue with packages from Microsoft, they also fail to update, seems to be related to this issue

 .\WingetUpgrade.ps1
Exception calling "Substring" with "2" argument(s): "Index and length must refer to a location within the string.
Parameter name: length"
At C:\Users\cyber\Downloads\WingetUpgrade.ps1:45 char:9
+         $available = $line.Substring($availableStart, $sourceStart -  ...
+         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : ArgumentOutOfRangeException


Name                                        Id                                      Version           AvailableVersion
----                                        --                                      -------           ----------------
TechPowerUp GPU-Z                           TechPowerUp.GPU-Z                       2.46.0            2.52.0
Visual Studio Build Tools 2017              Microsoft.VisualStudio.2017.BuildTools  > 15.8.9          15.9.51
Windows Software Development Kit - WindowΓÇ ª Microsoft.WindowsSDK                    < 10.0.22000.83 2 10.0.22621.2
VMware Workstation                          VMware.WorkstationPro                   16.1.0            17.0.0
Microsoft Visual C++ 2015-2022 RedistribuΓÇ ª Microsoft.VCRedist.2015+.x64            14.34.31823.3     14.34.31931.
Windows Software Development Kit - WindowΓÇ ª Microsoft.WindowsSDK                    10.0.22621.1      10.0.22621.2
2 packages have version numbers that cannot  be determined. Using "--include-unknow n" may show more    10.0.22621.2


Going to upgrade package TechPowerUp.GPU-Z
No installed package found matching input **criteria.**

@cybersholt
Copy link

cybersholt commented Jan 8, 2023

@geraldodinukwe80 fixed the errors in my fork.

@alkampfergit the substring error is because I had an extra line at the end of my winget upgrade command, I suspect the other user had similar output.

Example: 2 packages have version numbers that cannot be determined. Using "--include-unknown" may show more results.

@geraldodinukwe80
Copy link

Hi @cybersholt,
I already found why I was having the error
This is because my powershell returns some of the output of winget upgrade in German. So I already adjusted the variables used here to suit my enviroment.
Below is the part of the code where I needed to adjust the variables

$availableStart = $lines[$fl].IndexOf("Available")
$sourceStart = $lines[$fl].IndexOf("Source")

Available is returned by winget in german as "Verfügbar"
Source is returned by winget in german as "Quelle"

So I had to adjust them to suit my enviroment

Regards

@cybersholt
Copy link

Thanks so much @geraldodinukwe80, I'll come up with a more universal solution, I think we can just check that the current line has ID and current version, and that those values aren't empty.

@alkampfergit
Copy link
Author

Feel free to post modification here I'll update the script.

@roan0909
Copy link

roan0909 commented Feb 9, 2023

Hi

I did add language support and logging.
Now it works on both English and German.

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

Company

Winget with exclusions

Name

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
###==================================================================================================================================

Variables

class Software {
[string]$Name
[string]$Id
[string]$Version
[string]$AvailableVersion
}

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

Apps to exclude

$AppstoSkip = @("Microsoft.Office")

$AppstoSkip = @("Microsoft.Office","Google.Chrome")

###==================================================================================================================================

Log File

----------------------------------------------------------------------------

$LogFile = "Winget.txt"
$Dir = "C:_Logs\Winget"
IF(!(Test-Path $Dir)){mkdir $Dir}
$PathLogFile = $Dir + "" + $LogFile
$PathLogFile

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

Functions

###==============================================

LogEvent

###==============================================
Function LogEvent([string]$strLOGStatus, [string]$strLogMessage)
{
$GD = get-Date
"$GD-$strLOGStatus-$strLogMessage" | out-file $PathLogFile -append
switch($strLOGStatus)
{
"LOGInfo" {Write-Host $strLogMessage -ForegroundColor "green"}
"LOGError" {Write-Host $strLogMessage -ForegroundColor "red"}
"LOGWarning" {Write-Host $strLogMessage -ForegroundColor "Yellow"}
}
}

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

DATA Gathering Section

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
LogEvent "LOGInfo" ("--------------------------------------------------")
LogEvent "LOGInfo" ("--------- Starting Winget Upgrade Script - gathering DATA")

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

Getting the winget list

$upgradeResult = winget upgrade | Out-String
$lines = $upgradeResult.Split([Environment]::NewLine)

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

Find the line that starts with Name, it contains the header

$fl = 0
while (-not $lines[$fl].StartsWith("Name"))
{
$fl++
}

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

Get languge

$Lang = Get-WinSystemLocale | select -ExpandProperty name

IF( $lang -eq "en-US"){

English

Write-Host ("Language is English - " + $lang)

# $Lines has the header, where we can find the character Id, Available, Version and Source

$idStart = $lines[$fl].IndexOf("Id")
$versionStart = $lines[$fl].IndexOf("Version")
$availableStart = $lines[$fl].IndexOf("Available")
$sourceStart = $lines[$fl].IndexOf("Source")
}

IF( $lang -eq "de-DE"){

German

Write-Host ("Sprache ist Deutsch - " + $lang)

# $Lines has the header, where we can find the GERMAN character ID, Version, Verfügbar and Quelle

$idStart = $lines[$fl].IndexOf("ID")
$versionStart = $lines[$fl].IndexOf("Version")
$availableStart = $lines[$fl].IndexOf("Verfügbar")
$sourceStart = $lines[$fl].IndexOf("Quelle")

}

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

Now cycle in real package and split accordingly

$upgradeList = @()
For ($i = $fl + 1; $i -le $lines.Length; $i++)
{
$line = $lines[$i]
if ($line.Length -gt ($availableStart + 1) -and -not $line.StartsWith('-'))
{
$name = $line.Substring(0, $idStart).TrimEnd()
$id = $line.Substring($idStart, $versionStart - $idStart).TrimEnd()
$version = $line.Substring($versionStart, $availableStart - $versionStart).TrimEnd()
$available = $line.Substring($availableStart, $sourceStart - $availableStart).TrimEnd()
$software = [Software]::new()
$software.Name = $name;
$software.Id = $id;
$software.Version = $version
$software.AvailableVersion = $available;

    $upgradeList += $software
}

}

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

Winget Upgrade List

LogEvent "LOGInfo" ("Winget Upgrade List: ")
$upgradeList | Format-Table

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]

RUN Section

###[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]
###==============================================

Upgrading Software

###==============================================
Foreach ($package in $upgradeList)
{
IF (-not ($AppstoSkip -contains $package.Id))
{
LogEvent "LOGInfo" ("--------------------------------------------------")
LogEvent "LOGInfo" ("--------- Going to upgrade package $($package.id)")
& winget upgrade $package.id
}
else
{
LogEvent "LOGInfo" ("Skipped upgrade to package $($package.id)")
}
}

@nouseforname
Copy link

i've edited the script. It should work no matter the language and also is bit shorter

https://gist.github.com/nouseforname/a8b7ebcb9d0c05e380c7e3c81c300923

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment