[Python] Skript, das pwsh aktualisiert (sollte)

Motivation

Das Aktualisieren von PowerShell unter Windows ist ein manueller Vorgang, der jedoch langweilig geworden ist. Halbautomatisiert.

Verfassung

Wenn Sie pshupdate.py, den Hauptteil des Skripts, in Python ausführen, wird die Version automatisch überprüft und bei Bedarf aktualisiert (geplant).

Inhalt

Ich mache das nicht aufwendig, weil ich es nur so geschrieben habe, wie ich es tun soll, aber ich werde es schreiben.

Speicherung von Versionsdaten

Da es sich bei der Version um eine Zeichenfolge mit der Form "v3.2.1" handelt, wird der Rest nach dem Entfernen nur des ersten zusätzlichen Zeichens in ein Triplett von Zeichenfolgen umgewandelt, um daraus einen Versionstyp zu machen. Wenn Sie (Get-Host) .Version | Format-List * mit pwsh ausführen, erhalten Sie 6 Schlüssel, aber ich habe die letzten 3 nicht verwendet. 3 und 3 sind genug! Yoshi! In der zweiten Hälfte wird ein großer und kleiner Vergleich durchgeführt. Aus Gründen der Flexibilität habe ich beschlossen, None, 0 und -1 als Werte für "Minor" und "Build" zu behandeln, was das Ergebnis ärgerlich machte.

import re
from typing import NamedTuple

class Version(NamedTuple):
  """A triplet (Major, Minor, Build) of strings.
  Note that every element can have the None value.
  """
  Major: str
  Minor: str = '-1'
  Build: str = '-1'

  def formatVersion(self) ->str:
    """Return a string "<Major>.<Minor>.<Build>".\n
    Note that this function returns None if Version is None.
    """
    if (self.Major is None):
      return None
    else:
      ls = [self.Major]
    if (self.Minor != None) and (self.Minor != '-1'):
      ls.append(self.Minor)
    if (self.Build != None) and (self.Build != '-1'):
      ls.append(self.Build)
    return '.'.join(ls)

  def __eq__(self,other) -> bool:
    if(not isinstance(other,Version)):
      raise TypeError("Version data cannot compare to other type data.")
    
    if(self.Major != other.Major):
      return False
    elif(not self.Major):
      return True
    elif(self.Minor != other.Minor):
      if (self.Minor != None) and (self.Minor != '0') and (self.Minor != '-1'):
        return False
      elif (other.Minor != None) and (other.Minor != '0') and (other.Minor != '-1'):
        return False
    elif(self.Build != other.Build):
      if (self.Build != None) and (self.Build != '0') and (self.Build != '-1'):
        return False
      elif (other.Build != None) and (other.Build != '0') and (other.Build != '-1'):
        return False
    else:
      return True
  def __le__(self,other) -> bool:
    if(not isinstance(other,Version)):
      raise TypeError("Version data cannot compare to other type data.")
    
    if(self.Major.isdecimal()) and (other.Major.isdecimal()):
      if(int(self.Major) < int(other.Major)):
        return True
      else:
        pass
    elif(not self.Major.isdecimal()) and (not other.Major.isdecimal()):
      a, b = self.Major, other.Major
      mslf = re.search(r'^\d*',a)
      if mslf:
        anum, atxt = a[:mslf.end()], a[mslf.end():]
      else:
        anum, atxt = None, a

      moth = re.search(r'^\d*',b)
      if moth:
        bnum, btxt = b[:moth.end()], b[moth.end():]
      else:
        bnum, btxt = None, b

      if(int(anum) < int(bnum)):
        return True
      elif(int(anum)==int(bnum)):
        if(atxt < btxt):
          return True
        elif(atxt == btxt):
          pass
        else:
          return False
      else:
        return False
    else:
      raise ValueError("two Version data are not compareable.")
    
    if(self.Minor.isdecimal()) and (other.Minor.isdecimal()):
      if(int(self.Minor) < int(other.Minor)):
        return True
      else:
        pass
    elif(not self.Minor.isdecimal()) and (not other.Minor.isdecimal()):
      a, b = self.Minor, other.Minor
      mslf = re.search(r'^\d*',a)
      if mslf:
        anum, atxt = a[:mslf.end()], a[mslf.end():]
      else:
        anum, atxt = None, a

      moth = re.search(r'^\d*',b)
      if moth:
        bnum, btxt = b[:moth.end()], b[moth.end():]
      else:
        bnum, btxt = None, b

      if(int(anum) < int(bnum)):
        return True
      elif(int(anum)==int(bnum)):
        if(atxt < btxt):
          return True
        elif(atxt == btxt):
          pass
        else:
          return False
      else:
        return False
    else:
      raise ValueError("two Version data are not compareable.")

    if(self.Build.isdecimal()) and (other.Build.isdecimal()):
      if(int(self.Build) < int(other.Build)):
        return True
      else:
        return False
    elif(not self.Build.isdecimal()) and (other.Build.isdecimal()):
      a, b = self.Build, other.Build
      mslf = re.search(r'^\d*',a)
      if mslf:
        anum, atxt = a[:mslf.end()], a[mslf.end():]
      else:
        anum, atxt = None, a

      moth = re.search(r'^\d*',b)
      if moth:
        bnum, btxt = b[:moth.end()], b[moth.end():]
      else:
        bnum, btxt = None, b

      if(int(anum) < int(bnum)):
        return True
      elif(int(anum)==int(bnum)):
        if(atxt < btxt):
          return True
        else:
          return False
      else:
        return False
    else:
      raise ValueError("two Version data are not compareable.")

Ich habe auch eine Funktion erstellt, die eine Zeichenfolge in einen Versionstyp analysiert.

def parseVersion(s) ->Version:
  """input: a string or a list of strings
  
  Parse a string like "v1.0.4"
  """
  if(isinstance(s,bytes)):
    s = s.decode()
  if(isinstance(s,str)):
    match = re.search(r'\d*\.\d*\.?',s)
    if match:
      token = s[match.start():].split('.',2)
      if (len(token)==3):
        return Version(token[0],token[1],token[2])
      elif (len(token)==2):
        return Version(token[0],token[1],None)
      else:
        return Version(token[0],None,None)
    else:
      match = re.search(r'[0-9]*',s)
      if match:
        return Version(s[match.start():],None,None)
      else:
        raise ValueError("function parseVersion didn't parse argument.")
  elif(isinstance(s,list)):
    for x in s[0:3]:
      if(not isinstance(x,str)):
        raise TypeError("function parseVersion(s) takes a string or a list of strings as the argument.")
    try:
      (major,minor,build) = s[0:3]
    except ValueError:
      raise
    except:
      print("Unexpected error in function 'parseVersion'")
      raise
    return Version(major,minor,build)
  else:
    raise TypeError("function parseVersion(s) takes a string or a list of strings as the argument.")

Die neueste Version bekommen

Klicken Sie auf die Github-API, um Informationen zur neuesten Version zu erhalten. ``

import ssl, urllib.request
import json

head_accept = "application/vnd.github.v3+json"
host = "api.github.com"
key_release = "repos/PowerShell/PowerShell/releases/latest"


print("Latest version of pwsh ... ", end='', flush=True)

context = ssl.create_default_context()
url = ''.join(["https://",host,"/",key_release])
try:
  q = urllib.request.Request(url,headers={'accept':head_accept},method='GET')
  with urllib.request.urlopen(q, context=context) as res:
    content = json.load(res)
except urllib.error.HTTPError as err:
  print(err.code)
except urllib.error.URLError as err:
  print(err.reason)
except json.JSONDecodeError as err:
  print(err.msg)

v_latest = parseVersion(content['tag_name'])
print(v.formatVersion())

Holen Sie sich die lokale Version

Holen Sie sich Versionsinformationen mit pwsh -v. Da "Zeit" nur für die Gewichtsverarbeitung importiert wird, kann nichts gesagt werden, wenn gesagt wird, dass sie tatsächlich nicht benötigt wird.

import time
import subprocess

print("Current version of pwsh ... ", end='', flush=True)
time.sleep(0.5)

cpl_pwsh = subprocess.run("pwsh -v",capture_output=True)
v_local = parseVersion(cpl_pwsh.stdout.strip())
print(v_local.formatVersion())

Versionsvergleich und neueste Versionsinstallation

Ich habe nicht viel ausgearbeitet.

import ssl, urllib.request
import json

directory_download = R"path\to\directoryof\installer"

if(v_local < v_latest):
  print("Later version available.")
  print("Please wait...")

  aslist = content['assets']
  vlatest = attr_latest.getstr_version()
  targetname = '-'.join(["PowerShell",vlatest,"win-x64.msi"])
  targeturl = None
  for asset in aslist:
    if(asset['name'] == targetname):
      targeturl = asset['browser_download_url']
      break
  if targeturl:
    try:
      print("Downloading installer... ",end='',flush=True)
      with urllib.request.urlopen(targeturl,context=context) as res:
        dat_pack = res.read()
    except urllib.error.HTTPError as err:
      print(err.code)
    except urllib.error.URLError as err:
      print(err.reason)
    except json.JSONDecodeError as err:
      print(err.msg)
    
    try:
      path = '\\'.join([directory_download,targetname])
      f_installer = open(path,mode='xb')
      f_installer.write(dat_pack)
      f_installer.close()
    except OSError:
      print()
      raise
    except:
      print("Unexpected error occurred.")
      raise

    subprocess.run(path, stderr=subprocess.STDOUT)
  else:
    raise Exception("lost download url.")
elif(attr_pwsh.version == attr_latest.version):
  print("Your pwsh is latest version.\n")
else:
  raise Exception("unidentified exception occurred.")

Ausführungsbildschirm

Ich habe am Anfang und am Ende eine kleine Dekoration hinzugefügt, also sieht es so aus, wenn ich es laufen lasse. Natürlich ist es jetzt die neueste Version, also lasse ich es bis zum nächsten pwsh-Update, um zu testen, ob das Update ordnungsgemäß durchgeführt werden kann.

================================================
Powershell updater version 0.5.201114
  by Lat.S (@merliborn)

Latest version of pwsh ... 7.1.0
Current version of pwsh ... 7.1.0
Your pwsh is latest version.

Push return key to quit:
================================================

Impressionen

Ich bin froh, dass ich lange Zeit alles tun konnte, was ich wollte, z. B. HTTPS verwenden, die API drücken, um Informationen zu erhalten, und überhaupt etwas mit Python machen. Die Tatsache, dass Typanmerkungen aufgrund der Dekorationen in Ausdrücken geschrieben werden können, war für mich als Person, die normalerweise ein typisiertes Leben führt, sehr schockierend.

Recommended Posts

[Python] Skript, das pwsh aktualisiert (sollte)
Python-Update (2.6-> 2.7)
Skript-Python-Datei
Python-Skript-Skelett
Python-Skript-Profilerstellung
Importieren Sie ein Python-Skript
"Python Kit", das Python-Skripte von Swift aufruft
Python Memorandum (sequentielle Aktualisierung)
Ubuntu-Paket-Update-Skript
Was ist in dieser Variablen (wenn das Python-Skript ausgeführt wird)?
DynamoDB Script Memo (Python)
Die Einstellung, die Programmierer haben sollten (The Zen of Python)
Ein Python-Skript, das ein GTK-Bild (Clipboard) in einer Datei speichert.
Erstellen eines Python-Skripts, das die e-Stat-API unterstützt (Version 2)
Python-Skript, das den Inhalt zweier Verzeichnisse vergleicht
POST json mit Python 3-Skript
So aktualisieren Sie Pythons Tkinter auf 8.6
Automatisches Update des Python-Moduls
Führen Sie das Illustrator-Skript von Python aus
Pakete, die enthalten sein sollten
Aktualisieren Sie die Python, die Sie auf Ihrem Mac hatten, auf 3.7-> 3.8
[Python-Anfänger] Pip selbst aktualisieren
Beachten Sie, dass es Python 3 unterstützt
[Python] [Langfristig] 100 Artikel, die Qiita jetzt lesen sollte [Wöchentlich automatisch aktualisiert]
33 Zeichenfolgen, die in Python nicht als Variablennamen verwendet werden sollten
Ein Python-Skript, das auf dem Mac erstellte ._DS_Store- und ._ * -Dateien löscht