Updating PowerShell on Windows is a manual process, but it has become dull. Semi-automated.
If you run pshupdate.py, which is the main body of the script, in python, it will automatically check the version and update it if necessary (planned).
I'm not doing that elaborate because I just wrote it according to what I should do, but I will write it.
Since the version is a character string of such a form as "v3.2.1", the rest after removing only the first extra character is made into a triad of character strings to make it a Version type. If you do (Get-Host) .Version | Format-List *
with pwsh, you will get 6 keys, but I haven't used the latter 3 and 3 is enough! Yoshi!
The size comparison is implemented in the latter half. For some flexibility, I decided to treat the values of Minor
and Build
as None, 0, and -1 all the same, which made the result annoying.
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.")
I also created a function that parses a string into a Version type.
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.")
Hit the Github API to get information on the latest release. ``
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())
Get version information with pwsh -v
. Since time
is imported only for weight processing, nothing can be said when it is said that it is not actually needed.
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())
I haven't done much elaboration.
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.")
I added a little decoration at the beginning and the end, so when I run it, it looks like this. Of course, it's the latest version now, so I'll leave it until the next pwsh update to test whether the update can be done properly.
================================================
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:
================================================
I'm glad that I could use HTTPS, hit the API to get information, make something with python in the first place, and do everything I wanted to do for a long time. The fact that type annotations can be written in expressions because they are decorations was a shock to me as a person who usually lives a typed life.
Recommended Posts