tis-check-windows-eol
This package allows you to check whether the version of Windows installed on a workstation (client or server) is still supported by Microsoft or has reached its End Of Life (EOL).
The audit is performed automatically every 7 days to account for potential version upgrades, such as an update from Windows 10 22H2 to 24H2, or a migration from Windows 10 to Windows 11.
The result is recorded in the audit data under the key audit-windows-eol
Three types of results are possible :
- ✅
OK
→ The system is still supported by Microsoft. - ⚠️
WARNING
→ The system is at the end of support. - ❌
ERROR
→ The Windows version could not be correctly identified.
In case of an
ERROR
status, please contact the Tranquil IT team to adjust or correct the package code.
The package is monitored by our team and will be updated regularly based on new data published by Microsoft.
To benefit from the latest updates, it is recommended to :
- Clone the package from the Tranquil IT WAPT repository.
- Or run
update_package
from the WAPT console.
- package: tis-check-windows-eol
- name: Check Windows EOL
- version: 1-25
- categories: System,Security
- maintainer: WAPT Team,Tranquil IT,Flavien SCHELFAUT
- licence: proprietary_free
- locale: all
- target_os: windows
- architecture: all
- signature_date:
- size: 79.95 Ko
package : tis-check-windows-eol
version : 1-25
architecture : all
section : base
priority : optional
name : Check Windows EOL
categories : System,Security
maintainer : WAPT Team,Tranquil IT,Flavien SCHELFAUT
description : Detect whether your Windows version is actively supported or has reached its end-of-life status, including extended support phases
depends :
conflicts :
maturity : PROD
locale : all
target_os : windows
min_wapt_version : 2.5
sources :
installed_size :
impacted_process :
description_fr : Détermine si votre version de Windows est activement prise en charge ou si elle a atteint la fin de sa durée de vie, y compris les phases de prise en charge étendue.
description_pl : Określa, czy dana wersja systemu Windows jest aktywnie wspierana, czy też osiągnęła koniec okresu eksploatacji, w tym fazy rozszerzonego wsparcia.
description_de : Bestimmt, ob Ihre Windows-Version aktiv unterstützt wird oder das Ende ihrer Lebensdauer erreicht hat, einschließlich der Phasen der erweiterten Unterstützung.
description_es : Determina si su versión de Windows recibe soporte activo o si ha llegado al final de su vida útil, incluidas las fases de soporte ampliado.
description_pt : Determina se a sua versão do Windows tem suporte ativo ou se chegou ao fim da sua vida útil, incluindo as fases de suporte alargado.
description_it : Determina se la versione di Windows in uso è attivamente supportata o se ha raggiunto la fine del suo ciclo di vita, comprese le fasi di supporto esteso.
description_nl : Bepaalt of uw versie van Windows actief wordt ondersteund of het einde van zijn levensduur heeft bereikt, inclusief verlengde ondersteuningsfasen.
description_ru : Определяет, активно ли поддерживается ваша версия Windows или срок ее жизни подошел к концу, включая фазы расширенной поддержки.
audit_schedule : 7d
editor :
keywords :
licence : proprietary_free
homepage :
package_uuid : b9b5b4fb-b764-465b-8959-e9b993f67ee0
valid_from :
valid_until :
forced_install_on :
changelog :
min_os_version :
max_os_version :
icon_sha256sum : 3975cd47cad6ddc972ff4af8e9bdf7d2293b9c0f0b66db3d5b6ea4b0dcbd1916
signer : Tranquil IT
signer_fingerprint: 8c5127a75392be9cc9afd0dbae1222a673072c308c14d88ab246e23832e8c6bb
signature_date : 2025-08-26T11:04:18.000000
signed_attributes : package,version,architecture,section,priority,name,categories,maintainer,description,depends,conflicts,maturity,locale,target_os,min_wapt_version,sources,installed_size,impacted_process,description_fr,description_pl,description_de,description_es,description_pt,description_it,description_nl,description_ru,audit_schedule,editor,keywords,licence,homepage,package_uuid,valid_from,valid_until,forced_install_on,changelog,min_os_version,max_os_version,icon_sha256sum,signer,signer_fingerprint,signature_date,signed_attributes
signature : TtRE+gZqc2TEFVwZBOhOY8f9Jmw3cH2wOa/cP4rn266KFS4LfJrFikemXQSwt8Z3ARBJNrsfsqoR2bEPIEVpgkHKQSgJuuONGNQLPCseVJezKbsn5saA6rzPfPe+NxBssY14e7jpyh4syepw6BVSUnaMY/yGdbH0ehOi5A6Q9wCUG+u9rYUkxfZCpXJnKyQT5nr1W2CJOGCXXVfcQWZn6crSVo5UqO/rLhA/dpcenIk7DDnekChWpqt++/+kec5Rdr9AWHB7N6PIt9mkdTIgAQMXkFM9zQYbq154Y1D9BzcGlkXSlz8bGnVl6YlFXmbHa/OltzgHkcTz5MOgDLflKg==
# -*- coding: utf-8 -*-
from setuphelpers import *
from setupdevhelpers import *
from datetime import datetime
from typing import Optional
import re
def install():
# Cleanup old files
for file in ["windows_client.json", "windows_server.json"]:
dest_path = makepath(public_persistent_dir, file)
if isfile(dest_path):
remove_file(dest_path)
print("Copy file to persistent folder")
dest_path = makepath(public_persistent_dir, "windows_products.json")
if isfile(dest_path):
remove_file(dest_path)
filecopyto("windows_products.json", dest_path)
def audit():
if params.get("running_as_luti"):
return check_all_windows_release()
windows_eol_checker = WindowsEOLChecker()
status, release_end_date = windows_eol_checker.run()
WAPT.write_audit_data_if_changed(
"audit-windows-eol", "audit-windows-eol",
{'status': status, 'release_end_date': release_end_date}
)
return status
class WindowsEOLChecker:
def __init__(
self, debug=False, product_name: str = None, edition_id: str = None, windows_ver_prettyname: str = None,
esu_licenses: Optional[list] = None, installation_type: str = None, windows_ver: Version = None,
csd_version: str = None, release_id: str = None
):
self.debug = debug
self.product_name = product_name
self.edition_id = edition_id
self.windows_ver_prettyname = windows_ver_prettyname
self.esu_licenses = list() if esu_licenses is None else esu_licenses
self.installation_type = installation_type
self.windows_version = windows_ver if windows_ver else windows_version()
self.csd_version = csd_version
self.release_id = release_id
if not self.debug:
self.product_name = registry_get(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "ProductName")
self.edition_id = registry_get(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "EditionID")
self.windows_ver_prettyname = registry_get(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "DisplayVersion")
release_id = registry_readstring(HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\Windows NT\CurrentVersion', 'ReleaseId')
self.csd_version = registry_get(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "CSDVersion")
self.esu_licenses = self.get_esu_license_info()
if not release_id:
if Version(self.windows_version, 3) == Version('10.0.10240', 3):
release_id = "1507"
if Version(self.windows_version, 3) == Version('10.0.10586', 3):
release_id = "1511"
if not self.windows_ver_prettyname:
self.windows_ver_prettyname = release_id
if self.is_windows_client() and self.windows_version >= WindowsVersions.Windows11:
self.product_name = self.product_name.replace('Windows 10', 'Windows 11')
self.windows_product_filename = 'windows_products.json'
def is_windows_client(self):
installation_type = (
self.installation_type if self.debug and self.installation_type else
registry_readstring(HKEY_LOCAL_MACHINE, r"SOFTWARE\Microsoft\Windows NT\CurrentVersion", "InstallationType")
)
return installation_type == "Client"
def detect_esu_year(self, product_name):
match = re.search(r"Year\s?([1-9])", product_name, re.IGNORECASE)
return f"Extended Security Update Year {match.group(1)}" if match else "Unknown"
def get_esu_license_info(self):
"""
__Output__
Windows Server 2012 R2
------------------------
Name : Windows(R), Server-ESU-Year3 add-on for ServerDatacenter,ServerDatacenterCore,ServerDatacenterV,ServerDatacenterVCore,ServerStandard,ServerStandardCore,ServerStandardV,ServerStandardVCore
PartialProductKey : C4BPT
LicenseStatus : 1
Description : Windows(R) Operating System, VOLUME_MAK channel
LicenseFamily : Windows(R), Server-ESU-Year3 add-on for ServerDatacenter,ServerDatacenterCore,ServerDatacenterV,ServerDatacenterVCore,ServerStandard,ServerStandardCore,ServerStandardV,ServerStandardVCore
Windows 7 SP1
------------------------
Name Windows(R) 7, Client-ESU-Year3 add-on for Enterprise,EnterpriseE,EnterpriseN,Professional,ProfessionalE,ProfessionalN,Ultimate,UltimateE,UltimateN
ESUYear Windows(R) 7, Client-ESU-Year3 add-on for Enterprise,EnterpriseE,EnterpriseN,Professional,ProfessionalE,ProfessionalN,Ultimate,UltimateE,UltimateN
PartialProductKey XRFH7
Description Windows Operating System - Windows(R) 7, VOLUME_MAK channel
LicenseStatus 1
"""
try:
wmi = win32com_ensure_dispatch_patch("WbemScripting.SWbemLocator")
service = wmi.ConnectServer(".", "root\\cimv2")
except:
if os.path.isdir(os.path.join(tempfile.gettempdir(), 'gen_py')):
remove_tree(os.path.join(tempfile.gettempdir(), 'gen_py'), ignore_errors=True)
wmi = win32com_ensure_dispatch_patch("WbemScripting.SWbemLocator")
service = wmi.ConnectServer(".", "root\\cimv2")
def get_property_value_win32com(product, prop_name):
for prop in product.Properties_:
if prop.Name == prop_name:
return prop.Value
return None
query = (
"SELECT * FROM SoftwareLicensingProduct "
"WHERE ApplicationID='55c92734-d682-4d71-983e-d6ec3f16059f' "
"AND PartialProductKey IS NOT NULL AND LicenseStatus=1"
)
licenses = []
for product in service.ExecQuery(query):
name = get_property_value_win32com(product, "Name")
if name and any(x in name for x in ("ESU", "Extended Security")):
license_info = {
"name": name.split('for')[0],
"partial_product_key": product.PartialProductKey,
"license_status": product.LicenseStatus,
"description": product.Description,
"esu_year": self.detect_esu_year(name)
}
licenses.append(license_info)
return licenses
@staticmethod
def get_highest_esu_licence(esu_licences):
valid = [lic for lic in esu_licences if lic['esu_year'] != 'Unknown']
return max(valid, key=lambda x: int(x['esu_year'].split('Year')[-1])) if valid else None
def is_release_eol(self, product):
support_policy = product['SupportPolicy']
datetime_format_str = '%Y/%m/%d'
if support_policy == 'Modern':
release_end_date = datetime.strptime(product['ReleaseEndDate'], datetime_format_str)
elif support_policy == 'Fixed':
release_end_date = product['ExtendedEndDate'] if any(x in product['Product'] for x in ('LTSB', 'LTSC')) else product['MainstreamDate']
release_end_date = product['ReleaseEndDate'] if 'Extended Security Update' in product['Release'] else release_end_date
# Windows Server is LTSC/LTSB by default so get ExtendedEndDate
release_end_date = product['ExtendedEndDate'] if not self.is_windows_client() else release_end_date
release_end_date = datetime.strptime(release_end_date, datetime_format_str)
else:
return False, None
# Return a Tuple : bool, datetime
return datetime.now() > release_end_date, release_end_date.strftime(datetime_format_str)
def get_windows_report(self):
return {
"product_name": self.product_name,
"edition_id": self.edition_id,
"windows_ver_prettyname": self.windows_ver_prettyname,
"csd_version": self.csd_version,
"windows_version": str(self.windows_version),
"is_windows_client": self.is_windows_client(),
"esu_licenses": self.esu_licenses,
}
def run(self):
current_product = None
# In debug get the file in the package folder otherwise in public_persistent_dir
filepath = makepath(public_persistent_dir, self.windows_product_filename) if (not self.debug) or params.get("running_as_luti") else self.windows_product_filename
all_products = json_load_file(filepath)
# Windows Client
if self.is_windows_client():
if 'LTSC' in self.product_name:
current_product = next((i for i in all_products if self.product_name == i['Product']), None)
elif 'LTSB' in self.product_name:
# Doesn't have Edition in Product unlike LTSC ...
parts = self.product_name.split()
product_name = " ".join(parts[:2] + parts[3:])
current_product = next((i for i in all_products if product_name == i['Product']), None)
else:
windows_version = " ".join(self.product_name.split()[:2]) # Windows 10 Pro -> Windows 10
esu_licence = self.get_highest_esu_licence(self.esu_licenses)
filtered = [
i for i in all_products if windows_version in i['Product'] and
(
# For Windows >= 10 : Release is 20H2/22H2 etc
((self.windows_ver_prettyname and i['Release']) and i['Release'].split()[-1] == self.windows_ver_prettyname)
or
# For Windows 7 : Release can be 'Service Pack 1' or 'Extended Security Update Year 1'
((self.csd_version and i['Release']) and i['Release'] == (esu_licence['esu_year'] if esu_licence else self.csd_version))
)
]
# Match Windows Edition: Education/Enterprise/Pro etc
current_product = next((i for i in filtered if i['Edition'] in self.product_name), None)
# Windows Server
else:
filtered = [i for i in all_products if i['Product'] in self.product_name and i['Edition'] in self.product_name]
esu_licence = self.get_highest_esu_licence(self.esu_licenses)
if esu_licence:
current_product = next((i for i in filtered if esu_licence['esu_year'] in i['Release']), None)
else:
current_product = filtered[0] if filtered else None
# Check if the product found is EOL
if current_product:
eol, release_end_date = self.is_release_eol(current_product)
version_info = ((self.windows_ver_prettyname if self.windows_ver_prettyname else self.csd_version) if self.is_windows_client() else "")
full_name = f"{self.product_name} - {version_info}".strip()
if not eol:
print(f"[OK] Your {full_name} is supported until {release_end_date}")
return "OK", release_end_date
else:
print(f"[WARNING] {full_name} is EOL ! End date is {release_end_date}")
return "WARNING", release_end_date
else:
print("[ERROR] No matching product found. Please create a support report with the following information:\n")
print(json.dumps(self.get_windows_report(), indent=2))
return "ERROR"
def check_all_windows_release():
# Windows Server 2019 Standard Evaluation
checker_ws_2019_standard_eva = WindowsEOLChecker(
debug=True,
product_name="Windows Server 2019 Standard Evaluation",
edition_id="ServerStandardEval",
windows_ver_prettyname=None,
installation_type="Server",
windows_ver=Version('10.0.17763'),
esu_licenses=None,
release_id="1809"
)
# Windows Server 2012 R2 Standard
checker_ws_2012r2_standard = WindowsEOLChecker(
debug=True,
product_name="Windows Server 2012 R2 Standard",
edition_id="ServerStandard",
windows_ver_prettyname=None,
installation_type="Server",
windows_ver=Version("6.3.9600"),
esu_licenses=[
{
"name": "Windows(R), Server-ESU-Year2 add-on",
"partial_product_key": "C4BPT",
"license_status": 1,
"description": "Windows(R) Operating System, VOLUME_MAK channel",
"esu_year": "Extended Security Update Year 2"
},
{
"name": "Windows(R), Server-ESU-Year3 add-on",
"partial_product_key": "C4BPT",
"license_status": 1,
"description": "Windows(R) Operating System, VOLUME_MAK channel",
"esu_year": "Extended Security Update Year 3"
}
]
)
# Windows 7 SP1 - ESU Year 3
checker_w7_sp1_pro_esu = WindowsEOLChecker(
debug=True,
product_name="Windows 7 Professional",
edition_id="Professional",
windows_ver_prettyname=None,
installation_type="Client",
csd_version="Service Pack 1",
windows_ver=Version('6.1.7601'),
esu_licenses=[
{
"name": "Windows(R) 7, Client-ESU-Year3 add-on",
"partial_product_key": "XRFH7",
"license_status": 1,
"description": "Windows Operating System - Windows(R) 7, VOLUME_MAK channel",
"esu_year": "Extended Security Update Year 3"
},
# { # Not officialy supported
# "name": "Windows(R) 7, Client-ESU-Year6 add-on",
# "partial_product_key": "XRFH7",
# "license_status": 1,
# "description": "Windows Operating System - Windows(R) 7, VOLUME_MAK channel",
# "esu_year": "Extended Security Update Year 6"
# }
]
)
# Windows 7 SP1 Pro
checker_w7_sp1_pro = WindowsEOLChecker(
debug=True,
product_name="Windows 7 Professional",
edition_id="Professional",
windows_ver_prettyname=None,
installation_type="Client",
csd_version="Service Pack 1",
windows_ver=Version('6.1.7601'),
esu_licenses=None
)
# Windows 10 22H2 Pro
checker_w10_22h2_pro = WindowsEOLChecker(
debug=True,
product_name="Windows 10 Pro",
edition_id="Professional",
windows_ver_prettyname="22H2",
installation_type="Client",
windows_ver=Version('10.0.19045'),
esu_licenses=None,
release_id="2009"
)
# Windows 11 24H2 Pro
checker_w11_24h2_pro = WindowsEOLChecker(
debug=True,
product_name="Windows 11 Pro",
edition_id="Professional",
windows_ver_prettyname="24H2",
installation_type="Client",
windows_ver=Version('10.0.26100'),
esu_licenses=None
)
# Windows 11 24H2 Education
checker_w11_24h2_edu = WindowsEOLChecker(
debug=True,
product_name="Windows 11 Education",
edition_id="Education",
windows_ver_prettyname="24H2",
installation_type="Client",
windows_ver=Version('10.0.26100'),
esu_licenses=None,
release_id="2009"
)
# Windows 11 23H2 Education
checker_w11_23h2_pro_edu = WindowsEOLChecker(
debug=True,
product_name="Windows 11 Education",
edition_id="Education",
windows_ver_prettyname="23H2",
installation_type="Client",
windows_ver=Version('10.0.22631'),
esu_licenses=None,
release_id="2009"
)
# Windows 11 24H2 Pro Education
checker_w11_24h2_pro_edu = WindowsEOLChecker(
debug=True,
product_name="Windows 11 Pro Education",
edition_id="ProfessionalEducation",
windows_ver_prettyname="24H2",
installation_type="Client",
windows_ver=Version('10.0.26100'),
esu_licenses=None,
release_id="2009"
)
# Windows 10 Enterprise 2016 LTSB
checker_w10_enterprise_ltsb_2016 = WindowsEOLChecker(
debug=True,
product_name="Windows 10 Enterprise 2016 LTSB",
edition_id="EnterpriseS",
windows_ver_prettyname="1607",
installation_type="Client",
windows_ver=Version('10.0.14393'),
esu_licenses=None,
release_id=None
)
# Windows 10 Entreprise LTSC 2019
checker_w10_enterprise_ltsc_2019 = WindowsEOLChecker(
debug=True,
product_name="Windows 10 Enterprise LTSC 2019",
edition_id="EnterpriseS",
windows_ver_prettyname=None,
installation_type="Client",
windows_ver=Version('10.0.17763'),
esu_licenses=None,
release_id="1809"
)
list_of_checker = [
checker_ws_2019_standard_eva, checker_ws_2012r2_standard, checker_w7_sp1_pro_esu, checker_w7_sp1_pro,
checker_w10_22h2_pro, checker_w11_24h2_pro, checker_w11_24h2_edu, checker_w11_23h2_pro_edu, checker_w11_24h2_pro_edu,
checker_w10_enterprise_ltsb_2016, checker_w10_enterprise_ltsc_2019, WindowsEOLChecker()
]
audit_status = "OK"
for checker in list_of_checker:
result, _ = checker.run()
if result != "OK":
audit_status = result
return audit_status
if __name__ == '__main__':
check_all_windows_release()
from setuphelpers import *
from setupdevhelpers import *
import hashlib
from io import BytesIO
current_path = os.path.dirname(os.path.realpath(__file__))
if not current_path in sys.path:
sys.path.append(current_path)
import pylightxl as xl
def extract_data_from_excel(file_obj):
db = xl.readxl(file_obj)
ws = db.ws('lifecycle_data')
rows = list(ws.rows)
field_names = rows[6]
return [dict(zip(field_names, [item.strip(' *') for item in row])) for row in rows[7:]]
def update_package():
# Declaring local variables
package_updated = False
proxies = get_proxies()
if not proxies:
proxies = get_proxies_from_wapt_console()
app_name = control.name
# https://learn.microsoft.com/en-us/lifecycle/products/export/
# https://learn.microsoft.com/en-us/lifecycle/products/export/guidance
base_url = "https://app-omaha-prod.azurewebsites.net/api/PublishedListings/Export"
windows_product_json = f"windows_products.json"
windows_product_sha256 = f"windows_products.sha256"
sha256_old = json_load_file(windows_product_sha256) if isfile(windows_product_sha256) else None
download_url = f"{base_url}(endOfSupportYear=0,endOfSupportMonths=0,family='',group='')"
with BytesIO(wgets(download_url, proxies=proxies)) as f:
data = extract_data_from_excel(f)
sha256_new = hashlib.sha256(json.dumps(data).encode()).hexdigest()
if not sha256_old or sha256_new != sha256_old:
print(f"[UPDATE] New changes detected for {windows_product_json} !")
json_write_file(windows_product_json, data)
json_write_file(windows_product_sha256, sha256_new)
package_updated = True
if package_updated:
increment = int(control.version.rsplit('-', 1)[-1]) + 1
control.version = f"{control.get_software_version()}-{increment}"
control.save_control_to_wapt()
print("[SUCCESS] Package updated and version bumped")
else:
print("[INFO] No changes detected, package not updated")
return package_updated
cff6b01fc14c665c122c6ab6c0e69fab3caa6292e6c7df735ca3b6bef71b536a : WAPT/README.md
570dffbbe4b0c08d0d3b5a888c4bf9e4cb20fcee34ba0377001545f5ab53709f : WAPT/README_fr.md
38d056ab130f7bf7c481c12636a4e9959de36561d3dfcbe54c6e3571bc0c1dc3 : WAPT/certificate.crt
2c04738080e1d9889a47a9e13c090ed11d5d31046106011f2b64e2f392b6583c : WAPT/control
3975cd47cad6ddc972ff4af8e9bdf7d2293b9c0f0b66db3d5b6ea4b0dcbd1916 : WAPT/icon.png
ef2b663e14e4d76a3bbd75d36ce9eb129073f663acad0359d671c744b559b0d9 : luti.json
dcc79ea10bfe84a27cf76bdd9ee4cc86b17f78d08ca6f311dff71c47f9828451 : pylightxl.py
93d1938a92aa8c9ea333d32e6d91fdb7bd67b4de0d8321a08f80a2f206a5fd15 : setup.py
f6869c0ac76538930c40a970a45167c286380e66fe093a776347ca0f5092dd7f : update_package.py
5d229d287d758f9756850970c23e6ee5433ccd04a5ff7cb9eb96adc94f533714 : windows_products.json
31f2f95e614ac0ab8206369433c479e39302e4eb44fb6f3615fffa0700e7d330 : windows_products.sha256