diff --git a/utils/osdetail/colors_windows.go b/utils/osdetail/colors_windows.go new file mode 100644 index 0000000..214b755 --- /dev/null +++ b/utils/osdetail/colors_windows.go @@ -0,0 +1,50 @@ +package osdetail + +import ( + "sync" + + "golang.org/x/sys/windows" +) + +var ( + colorSupport bool + + colorSupportChecked bool + checkingColorSupport sync.Mutex +) + +// EnableColorSupport tries to enable color support for cmd on windows and returns whether it is enabled. +func EnableColorSupport() bool { + checkingColorSupport.Lock() + defer checkingColorSupport.Unlock() + + if !colorSupportChecked { + colorSupport = enableColorSupport() + } + return colorSupport +} + +func enableColorSupport() bool { + if IsWindowsVersion("10.") { + + // check if windows.Stdout is file + if windows.GetFileInformationByHandle(windows.Stdout, &windows.ByHandleFileInformation{}) == nil { + return false + } + + var mode uint32 + err := windows.GetConsoleMode(windows.Stdout, &mode) + if err == nil { + if mode&windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING == 0 { + mode |= windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING + err = windows.SetConsoleMode(windows.Stdout, mode) + if err != nil { + return false + } + } + return true + } + } + + return false +} diff --git a/utils/osdetail/dnscache_windows.go b/utils/osdetail/dnscache_windows.go new file mode 100644 index 0000000..dfe364a --- /dev/null +++ b/utils/osdetail/dnscache_windows.go @@ -0,0 +1,17 @@ +package osdetail + +import ( + "os/exec" +) + +// EnableDNSCache enables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 2 (Automatic). +// A reboot is required for this setting to take effect. +func EnableDNSCache() error { + return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "2", "/f").Run() +} + +// DisableDNSCache disables the Windows Service "DNS Client" by setting the registry value "HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\services\Dnscache" to 4 (Disabled). +// A reboot is required for this setting to take effect. +func DisableDNSCache() error { + return exec.Command("reg", "add", "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\services\\Dnscache", "/v", "Start", "/t", "REG_DWORD", "/d", "4", "/f").Run() +} diff --git a/utils/osdetail/service_windows.go b/utils/osdetail/service_windows.go new file mode 100644 index 0000000..5774db0 --- /dev/null +++ b/utils/osdetail/service_windows.go @@ -0,0 +1,112 @@ +package osdetail + +import ( + "errors" + "fmt" + "os/exec" + "strings" + "time" +) + +// Service Status +const ( + StatusUnknown uint8 = iota + StatusRunningStoppable + StatusRunningNotStoppable + StatusStartPending + StatusStopPending + StatusStopped +) + +// Exported errors +var ( + ErrServiceNotStoppable = errors.New("the service is not stoppable") +) + +// GetServiceStatus returns the current status of a Windows Service (limited implementation). +func GetServiceStatus(name string) (status uint8, err error) { + + output, err := exec.Command("sc", "query", name).Output() + if err != nil { + return StatusUnknown, fmt.Errorf("failed to query service: %s", err) + } + outputString := string(output) + + switch { + case strings.Contains(outputString, "RUNNING"): + if strings.Contains(outputString, "NOT_STOPPABLE") { + return StatusRunningNotStoppable, nil + } + return StatusRunningStoppable, nil + case strings.Contains(outputString, "STOP_PENDING"): + return StatusStopPending, nil + case strings.Contains(outputString, "STOPPED"): + return StatusStopped, nil + case strings.Contains(outputString, "START_PENDING"): + return StatusStopPending, nil + } + + return StatusUnknown, errors.New("unknown service status") +} + +// StopService stops a Windows Service. +func StopService(name string) (err error) { + pendingCnt := 0 + for { + + // get status + status, err := GetServiceStatus(name) + if err != nil { + return err + } + + switch status { + case StatusRunningStoppable: + err := exec.Command("sc", "stop", name).Run() + if err != nil { + return fmt.Errorf("failed to stop service: %s", err) + } + case StatusRunningNotStoppable: + return ErrServiceNotStoppable + case StatusStartPending, StatusStopPending: + pendingCnt++ + if pendingCnt > 50 { + return errors.New("service stuck in pending status (5s)") + } + case StatusStopped: + return nil + } + + time.Sleep(100 * time.Millisecond) + } +} + +// SartService starts a Windows Service. +func SartService(name string) (err error) { + pendingCnt := 0 + for { + + // get status + status, err := GetServiceStatus(name) + if err != nil { + return err + } + + switch status { + case StatusRunningStoppable, StatusRunningNotStoppable: + return nil + case StatusStartPending, StatusStopPending: + pendingCnt++ + if pendingCnt > 50 { + return errors.New("service stuck in pending status (5s)") + } + case StatusStopped: + err := exec.Command("sc", "start", name).Run() + if err != nil { + return fmt.Errorf("failed to stop service: %s", err) + } + } + + time.Sleep(100 * time.Millisecond) + } +} diff --git a/utils/osdetail/version_windows.go b/utils/osdetail/version_windows.go new file mode 100644 index 0000000..7435244 --- /dev/null +++ b/utils/osdetail/version_windows.go @@ -0,0 +1,54 @@ +package osdetail + +import ( + "os/exec" + "regexp" + "strings" + "sync" +) + +var ( + versionRe = regexp.MustCompile(`[0-9\.]+`) + + windowsVersion string + + fetching sync.Mutex + fetched bool +) + +func fetchVersion() { + if !fetched { + fetched = true + + output, err := exec.Command("cmd", "ver").Output() + if err != nil { + return + } + + match := versionRe.Find(output) + if match == nil { + return + } + + windowsVersion = string(match) + } +} + +// WindowsVersion returns the current Windows version. +func WindowsVersion() string { + fetching.Lock() + defer fetching.Unlock() + fetchVersion() + + return windowsVersion +} + +// IsWindowsVersion returns whether the given version matches (HasPrefix) the current Windows version. +func IsWindowsVersion(version string) bool { + fetching.Lock() + defer fetching.Unlock() + fetchVersion() + + // TODO: we can do better. + return strings.HasPrefix(windowsVersion, version) +} diff --git a/utils/osdetail/version_windows_test.go b/utils/osdetail/version_windows_test.go new file mode 100644 index 0000000..251fb22 --- /dev/null +++ b/utils/osdetail/version_windows_test.go @@ -0,0 +1,9 @@ +package osdetail + +import "testing" + +func TestWindowsVersion(t *testing.T) { + if WindowsVersion() == "" { + t.Fatal("could not get windows version") + } +}