wingetposh.psm1

class upgradeSoftware {
  [string]$Name
  [string]$Id
  [string]$Version
  [string]$AvailableVersion
  [string]$Source
}

class installSoftware {
  [string]$Name
  [string]$id
  [string]$Version
  [string]$Source
}

class column {
  [string]$Name
  [Int16]$Position
  [Int16]$Len
}

class Frame {
  [char]$UL
  [char]$UR
  [char]$TOP
  [char]$LEFT
  [char]$RIGHT
  [char]$BL
  [char]$BR
  [char]$BOTTOM
  [char]$LEFTSPLIT
  [char]$RIGHTSPLIT

  Frame (
      [bool]$Double
  ) {
      if ($Double) {
          $this.UL = "╔"
          $this.UR = "╗"
          $this.TOP = "═"
          $this.LEFT = "║"
          $this.RIGHT = "║"
          $this.BL = "╚"
          $this.BR = "╝"
          $this.BOTTOM = "═"
          $this.LEFTSPLIT = "⊫"
      }
      else {
          $this.UL = "┌"
          $this.UR = "┐"
          $this.TOP = "─"
          $this.LEFT = "│"
          $this.RIGHT = "│"
          $this.BL = "└"
          $this.BR = "┘"
          $this.BOTTOM = "─"
      }
  }
}

$Single = [Frame]::new($false)
$Double = [Frame]::new($true)

class window {
  [int]$X
  [int]$Y
  [int]$W
  [int]$H
  [Frame]$frameStyle
  [System.ConsoleColor]$frameColor
  [string]$title = ""
  [System.ConsoleColor]$titleColor
  [string]$footer = ""

  window(
      [int]$X,
      [int]$y,
      [int]$w,
      [int]$h,
      [bool]$Double,
      [System.ConsoleColor]$color = "White"
  ) {
      $this.X = $X
      $this.Y = $y
      $this.W = $W
      $this.H = $H
      $this.frameStyle = [Frame]::new($Double)
      $this.frameColor = $color
    
  }

  window(
      [int]$X,
      [int]$y,
      [int]$w,
      [int]$h,
      [bool]$Double,
      [System.ConsoleColor]$color = "White",
      [string]$title = "",
      [System.ConsoleColor]$titlecolor = "Blue"
  ) {
      $this.X = $X
      $this.Y = $y
      $this.W = $W
      $this.H = $H
      $this.frameStyle = [Frame]::new($Double)
      $this.frameColor = $color
      $this.title = $title
      $this.titleColor = $titlecolor
  }

  [void] setPosition(
      [int]$X,
      [int]$Y
  ) {
      [System.Console]::SetCursorPosition($X,$Y)
  }

  [void] drawWindow() {
      [System.Console]::CursorVisible = $false
      $this.setPosition($this.X,$this.Y)
    

      $bloc1 = "".PadLeft($this.W - 2, $this.frameStyle.TOP)
      $blank = "".PadLeft($this.W - 2, " ") 
      Write-Host $this.frameStyle.UL -NoNewline -ForegroundColor $this.frameColor
      Write-Host $bloc1 -ForegroundColor $this.frameColor -NoNewline
      Write-Host $this.frameStyle.UR -ForegroundColor $this.frameColor

      for ($i = 1; $i -lt $this.H; $i++) {
          $Y2 = $this.Y + $i
          $X2 = $this.X + $this.W - 1
          $this.setPosition($this.X, $Y2)
          Write-Host $this.frameStyle.LEFT -ForegroundColor $this.frameColor
        
          $X3 = $this.X + 1
          $this.setPosition($X3, $Y2)
          Write-Host $blank 
        
          $this.setPosition($X2, $Y2)
          Write-Host $this.frameStyle.RIGHT -ForegroundColor $this.frameColor
      }

      $Y2 = $this.Y + $this.H
      $this.setPosition( $this.X, $Y2)
      $bloc1 = "".PadLeft($this.W - 2, $this.frameStyle.BOTTOM)
      Write-Host $this.frameStyle.BL -NoNewline -ForegroundColor $this.frameColor
      Write-Host $bloc1 -ForegroundColor $this.frameColor -NoNewline
      Write-Host $this.frameStyle.BR -ForegroundColor $this.frameColor
      $this.drawTitle()
      $this.drawFooter()
  }

  [void] drawTitle() {
      if ($this.title -ne "") {
          $local:X = $this.x + 2
          $this.setPosition($local:X, $this.Y)
          Write-Host "| " -NoNewline -ForegroundColor $this.frameColor
          $local:X = $local:X + 2
          $this.setPosition($local:X,$this.Y)
          Write-Host $this.title -NoNewline -ForegroundColor $this.titleColor
          $local:X = $local:X + $this.title.Length
          $this.setPosition($local:X, $this.Y)
          Write-Host " |" -NoNewline -ForegroundColor $this.frameColor
      }
  }

  [void] drawFooter() {
      if ($this.footer -ne "") {
        $local:x = ($this.W - ($this.footer.Length + 6))
          $local:Y = $this.Y + $this.h
          $this.setPosition($local:X, $local:Y)
          Write-Host "| " -NoNewline -ForegroundColor $this.frameColor
          $local:X = $local:X + 2
          $this.setPosition($local:X, $local:Y)
          Write-Host $this.footer -NoNewline -ForegroundColor $this.titleColor
          $local:X = $local:X + $this.footer.Length
          $this.setPosition($local:X, $local:Y)
          Write-Host " |" -NoNewline -ForegroundColor $this.frameColor
      }
  }

  [void] clearWindow() {
      $local:blank = "".PadLeft($this.W - 2, " ") 
      for ($i = 1; $i -lt $this.H; $i++) {
          $this.setPosition(($this.X + 1), ($this.Y + $i))
          Write-Host $blank 
      } 
  }
}

function getColumnsHeaders {
  param(
    [parameter (
      Mandatory
    )]
    [string]$columsLine   
  )

  $tempCols = $columsLine.Split(" ")
  $cols = @()
  $result = @()
  foreach ($column in $tempCols) {
    if ($column.Trim() -ne "") {
      $cols += $column
    }
  }
  
  $i = 0
  while ($i -lt $Cols.Length) {
    $pos = $columsLine.IndexOf($Cols[$i])
    if ($i -eq $Cols.Length) {
      #Last Column
      $len = $columsLine.Length - $pos
    }
    else {
      #Not Last Column
      $pos2 = $columsLine.IndexOf($Cols[$i + 1])
      $len = $pos2 - $pos
    }
    $acolumn = [column]::new()
    $acolumn.Name = $Cols[$i]
    $acolumn.Position = $pos
    $acolumn.Len = $len
    $result += $acolumn
    $i++
  }
  $result
}

function _wgList {
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 
  $command = "winget list"
  
  $SearchResult = Invoke-Expression $command | Out-String
  $lines = $SearchResult.Split([Environment]::NewLine)

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

  $columns =  getColumnsHeaders -columsLine $lines[$fl-1]
  
  $idStart = $Columns[1].Position
  $versionStart = $Columns[2].Position
  #$availableStart = $lines[$fl].IndexOf("Disponible")
  $sourceStart = $columns[3].Position

  $InstalledList = @()
  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, $SourceStart - $versionStart).TrimEnd()
      #$available = $line.Substring($availableStart, $sourceStart - $availableStart).TrimEnd()
      $source = $line.Substring($sourceStart, $line.Length - $sourceStart).TrimEnd()
      if ($source -ne "") {
        $software = [installSoftware]::new()
        $software.Name = $name;
        $software.Id = $id;
        $software.Version = $version
        $software.Source = $source
        $InstalledList += $software
      }
    }
  }
  $installedList
}

# function check_ocgv {
# if(-not (Get-Module Microsoft.PowerShell.ConsoleGuiTools -ListAvailable)){
# Install-Module Microsoft.PowerShell.ConsoleGuiTools -Scope CurrentUser -Force
# }
# }

function _wgSearch {
  param(
    [parameter (
      Mandatory,
      HelpMessage = "Package (or part) name to search"
    )]
    [string]$search,
    # Store Filter
    [Parameter(
      HelpMessage = "store to filter on"
    )]
    [string]
    $Store    
  )

  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 

  $command = "winget search --name ${search}"

  if ($Store -ne "") {
    $command = $command + " --source " + $Store
  }

  $SearchResult = Invoke-Expression $command | Out-String
  $lines = $SearchResult.Split([Environment]::NewLine)

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

  $columns =  getColumnsHeaders -columsLine $lines[$fl-1]

  $idStart = $columns[1].Position
  $versionStart = $columns[2].Position
  if ($store -eq "") {
    $sourceStart = $columns[3].Position
  }
  $SearchList = @()
  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()
          
      if ($Store -eq "") {
        $version = $line.Substring($versionStart, $sourceStart - $versionStart).TrimEnd()
        $source = $line.Substring($sourceStart, $line.Length - $sourceStart).TrimEnd()
      }
      else {
        $version = $line.Substring($versionStart, $line.Length - $versionStart).TrimEnd()
        $source = $store
      }
      $software = [installSoftware]::new()
      $software.Name = $name;
      $software.Id = $id;
      $software.Version = $version
      $software.Source = $source;
      $SearchList += $software
    }
  }
  $SearchList
}

function wgUpgradable {
  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 
  $command = "winget upgrade --include-unknown"
  
  $SearchResult = Invoke-Expression $command | Out-String
  $lines = $SearchResult.Split([Environment]::NewLine)

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

  if ($fl -lt $lines.Length) {

  $columns =  getColumnsHeaders -columsLine $lines[$fl-1]
  
  $idStart = $Columns[1].Position
  $versionStart = $Columns[2].Position
  $availableStart = $columns[3].Position
  $sourceStart = $columns[4].Position

  $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()
      $source = $line.Substring($sourceStart, $line.Length - $sourceStart).TrimEnd()
      $software = [upgradeSoftware]::new()
      $software.Name = $name;
      $software.Id = $id;
      $software.Version = $version
      $software.AvailableVersion = $available
      $software.Source = $source
      $upgradeList += $software
    }
  }
  $upgradeList
}
}

<#
.SYNOPSIS
  Displays a list of installed packages
.DESCRIPTION
  Show-WGList displays a grid view of all the packages installed on the system.
  Each line is selectable. Accepting the selection with Enter returns an array of objects.
  Pressing Escape quits the grid with no furher action.
.EXAMPLE
  Show-WGList | Select-Object -Property Id | Uninstall-WGPackage
  In this example, the selected package in the grid will be uninstalled from the computer.
#>

function Show-WGList {
  param(
    [switch]$Single
  )
  # check_ocgv
  if ($single)
  {
    $list = _wgList | Out-ConsoleGridView -Title 'Installed Packages' -OutputMode Single
  }
  else {
    $list = _wgList | Out-ConsoleGridView -Title 'Installed Packages'  
  }
  return $list
  
}

function Show-WGUpdatables {
  # Parameter help description
  param(
  [Switch]
  $Multiple
  )
  # check_ocgv
  if (-not $Multiple) {
  $list = wgUpgradable | Out-ConsoleGridView -Title 'Upgradable Packages' -OutputMode Single
  }
  else {
    $list = wgUpgradable | Out-ConsoleGridView -Title 'Upgradable Packages'
  }
  # Write-Host $list
  return $list
}

function Search-WGPackage {
  param(
    [parameter (
      HelpMessage = "Package (or part) name to search"
    )]
    [string]$search,
    # Store Filter
    [Parameter(
      HelpMessage = "store to filter on"
    )]
    [string]
    $Store,
    [Switch]$Interactive,
    [Switch]$Install    
  )
  # check_ocgv
  if ($Interactive -or [string]::IsNullOrEmpty($search))
  {
    [System.Console]::Clear()
    $search = getSearchTerms
    [System.Console]::Clear()
  }

  $packages = _wgSearch $search $Store | Out-ConsoleGridView -Title "Search Package" -OutputMode Single
  if ($Install) {
    $packages | Select-Object -Property id | Install-WGPackage
  } else {
    return $packages
  }
}

function getSearchTerms {
  $WinWidth = [System.Console]::WindowWidth
    $X = 0
    $Y = [System.Console]::WindowHeight -6
    $WinHeigt = 4

    $win = [window]::new($X, $Y, $WinWidth, $WinHeigt, $false, "White");
  $win.title = "Search"
  $Win.titleColor = "Green"
  $win.footer = "[Enter] : Accept [Ctrl-C] : Abort"
  $win.drawWindow();
  $win.setPosition($X+2, $Y+2);
  [System.Console]::Write('Package : ')
  [system.console]::CursorVisible = $true
  $pack = [System.Console]::ReadLine()
  return $pack
}

function Install-WGPackage {
  [CmdletBinding()]
  param (
    [Parameter(ValueFromPipeline)]
    [upgradeSoftware[]] $inObj
  )

  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 
  $id = $inObj.id
  $command = "winget install '$id'"
  Invoke-Expression $command
}

function Uninstall-WGPackage {
  [CmdletBinding()]
  param (
    [Parameter(ValueFromPipeline)]
    $inObj,
    [Parameter()]
    [Switch]$Interactive
  )

  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 

  if ($PSBoundParameters.ContainsKey('inObj')) {
    if ($PSBoundParameters.ContainsKey('Interactive')) {
      Write-Error "-Interactive switch cannot be used with pipeline variable"
      return
    }
  }

  if ($Interactive) {
    $pkg = Show-WGList -Single | Select-Object -Property id,Name
    if (-not $pkg) {
      return
    }
    $ids = $pkg
  }
  else {
    $ids = $inObj
  }


  $ids | ForEach-Object {
    $id = $_.id;
    $name = $_.name
    if (-not [string]::IsNullOrEmpty($id)) {
      Write-Host "Uninstalling : " -NoNewline
      Write-Host "$name ($id)" -ForegroundColor DarkCyan
      $command = "winget uninstall '$id' --Force"
      #Write-Host $command
      Invoke-Expression $command
    }
    else {
      Write-Warning "Cannot uninstall unknown package"
    }
  }



  # $id = $inObj.id
  # $command = "winget uninstall '$id'"
  # Invoke-Expression $command
}

function Update-WGPackage {
  [CmdletBinding()]
  param (
    [Parameter(ValueFromPipeline)]
    [upgradeSoftware] $inObj,
    [Parameter()]
    [Switch]$Interactive,
    [Switch]$Force
  )

  [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 
  if ($PSBoundParameters.ContainsKey('inObj')) {
    if ($PSBoundParameters.ContainsKey('Interactive')) {
      Write-Error "-Interactive switch cannot be used with pipeline variable"
      return
    }
  }
 
  if ($Interactive) {
    $pkg = Show-WGUpdatables -Multiple | Select-Object -Property id,Name
    if (-not $pkg) {
      return
    }
    $ids = $pkg
  }
  else {
    $ids = $inObj
  }

  # Write-Host $ids
  $forceinstall = ""
  if ($Force) {
    $forceinstall = "--accept-package-agreements"  
  }
  
  $ids | ForEach-Object {
    $id = $_.id;
    $name = $_.name
    if (-not [string]::IsNullOrEmpty($id)) {
      Write-Host "Updating : " -NoNewline
      Write-Host "$name ($id)" -ForegroundColor DarkCyan
      $command = "winget upgrade '$id' $forceinstall"
      # Write-Host $command
      Invoke-Expression $command
    }
    else {
      Write-Warning "Cannot update unknown package"
    }
  }
  
}


#Export-ModuleMember Get-WGList, Get-WGSearch, Get-WGUpgrade, Set-WGInstall, Set-WGRemove