github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/core/os/base_windows.go (about)

     1  // Copyright 2024 Canonical Ltd.
     2  // Licensed under the LGPLv3, see LICENCE file for details.
     3  
     4  package os
     5  
     6  import (
     7  	"os"
     8  	"strings"
     9  
    10  	"github.com/juju/errors"
    11  	"golang.org/x/sys/windows/registry"
    12  
    13  	corebase "github.com/juju/juju/core/base"
    14  )
    15  
    16  var (
    17  	// currentVersionKey is defined as a variable instead of a constant
    18  	// to allow overwriting during testing
    19  	currentVersionKey = "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"
    20  
    21  	// isNanoKey determines the registry key that can be queried to determine whether
    22  	// a machine is a nano machine
    23  	isNanoKey = "Software\\Microsoft\\Windows NT\\CurrentVersion\\Server\\ServerLevels"
    24  
    25  	// Windows versions come in various flavors:
    26  	// Standard, Datacenter, etc. We use string prefix match them to one
    27  	// of the following. Specify the longest name in a particular series first
    28  	// For example, if we have "Win 2012" and "Win 2012 R2", we specify "Win 2012 R2" first.
    29  	// We need to make sure we manually update this list with each new windows release.
    30  	windowsVersionMatchOrder = []string{
    31  		"Hyper-V Server 2012 R2",
    32  		"Hyper-V Server 2012",
    33  		"Windows Server 2008 R2",
    34  		"Windows Server 2012 R2",
    35  		"Windows Server 2012",
    36  		"Hyper-V Server 2016",
    37  		"Windows Server 2016",
    38  		"Windows Server 2019",
    39  		"Windows Storage Server 2012 R2",
    40  		"Windows Storage Server 2012",
    41  		"Windows Storage Server 2016",
    42  		"Windows 7",
    43  		"Windows 8.1",
    44  		"Windows 8",
    45  		"Windows 10",
    46  		"Windows 11",
    47  	}
    48  
    49  	// windowsVersions is a mapping consisting of the output from
    50  	// the following WMI query: (gwmi Win32_OperatingSystem).Name
    51  	windowsVersions = map[string]string{
    52  		"Hyper-V Server 2012 R2":         "2012hvr2",
    53  		"Hyper-V Server 2012":            "2012hv",
    54  		"Windows Server 2008 R2":         "2008r2",
    55  		"Windows Server 2012 R2":         "2012r2",
    56  		"Windows Server 2012":            "2012",
    57  		"Hyper-V Server 2016":            "2016hv",
    58  		"Windows Server 2016":            "2016",
    59  		"Windows Server 2019":            "2019",
    60  		"Windows Storage Server 2012 R2": "2012r2",
    61  		"Windows Storage Server 2012":    "2012",
    62  		"Windows Storage Server 2016":    "2016",
    63  		"Windows Storage Server 2019":    "2019",
    64  		"Windows 7":                      "7",
    65  		"Windows 8.1":                    "81",
    66  		"Windows 8":                      "8",
    67  		"Windows 10":                     "10",
    68  		"Windows 11":                     "11",
    69  	}
    70  
    71  	// windowsNanoVersions is a mapping from the product name
    72  	// stored in registry to a juju defined nano-series
    73  	// On the nano version so far the product name actually
    74  	// is identical to the correspondent main windows version
    75  	// and the information about it being nano is stored in
    76  	// a different place.
    77  	windowsNanoVersions = map[string]string{
    78  		"Windows Server 2016": "2016nano",
    79  	}
    80  )
    81  
    82  func getVersionFromRegistry() (string, error) {
    83  	k, err := registry.OpenKey(registry.LOCAL_MACHINE, currentVersionKey, registry.QUERY_VALUE)
    84  	if err != nil {
    85  		return "", errors.Trace(err)
    86  	}
    87  	defer k.Close()
    88  	s, _, err := k.GetStringValue("ProductName")
    89  	if err != nil {
    90  		return "", errors.Trace(err)
    91  	}
    92  
    93  	return s, nil
    94  }
    95  
    96  func readBase() (corebase.Base, error) {
    97  	ver, err := getVersionFromRegistry()
    98  	if err != nil {
    99  		return corebase.Base{}, errors.Trace(err)
   100  	}
   101  
   102  	var lookAt = windowsVersions
   103  
   104  	isNano, err := isWindowsNano()
   105  	if err != nil && os.IsNotExist(err) {
   106  		return corebase.Base{}, errors.Trace(err)
   107  	}
   108  	if isNano {
   109  		lookAt = windowsNanoVersions
   110  	}
   111  
   112  	for _, value := range windowsVersionMatchOrder {
   113  		if strings.HasPrefix(ver, value) {
   114  			if val, ok := lookAt[value]; ok {
   115  				return corebase.ParseBase("win", val)
   116  			}
   117  		}
   118  	}
   119  	return corebase.Base{}, errors.Errorf("unknown series %q", ver)
   120  }
   121  
   122  func isWindowsNano() (bool, error) {
   123  	k, err := registry.OpenKey(registry.LOCAL_MACHINE, isNanoKey, registry.QUERY_VALUE)
   124  	if err != nil {
   125  		return false, errors.Trace(err)
   126  	}
   127  	defer k.Close()
   128  
   129  	s, _, err := k.GetIntegerValue("NanoServer")
   130  	if err != nil {
   131  		return false, errors.Trace(err)
   132  	}
   133  	return s == 1, nil
   134  }