//go:build windows package main // Is the WebView2 runtime actually installed? // // It is the one dependency OpsLog cannot ship inside its own executable, and // its absence is invisible: the process starts, Wails fails to create the // browser environment, and on some machines that failure arrives as a silent // exit rather than as an error we can print. So the version is read straight // from where the runtime registers itself, and written to the startup log // BEFORE the window is attempted — a log that says "WebView2: not found" // answers the whole question in one line. import ( "fmt" "strings" "golang.org/x/sys/windows/registry" ) // webview2Client is the runtime's own update-client GUID, the key it registers // its version under. Documented by Microsoft for exactly this check. const webview2Client = `Software\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}` // webView2Version returns the installed runtime version, or "" if there is none. // // Both hives: a per-machine install writes to HKLM (and to the WOW6432 view of // it on 64-bit Windows, which is where it usually lands), a per-user install to // HKCU. Missing all three is the answer that matters. func webView2Version() string { type place struct { key registry.Key access uint32 } for _, p := range []place{ {registry.CURRENT_USER, registry.QUERY_VALUE}, {registry.LOCAL_MACHINE, registry.QUERY_VALUE}, {registry.LOCAL_MACHINE, registry.QUERY_VALUE | registry.WOW64_32KEY}, } { k, err := registry.OpenKey(p.key, webview2Client, p.access) if err != nil { continue } v, _, err := k.GetStringValue("pv") k.Close() if err == nil && v != "" && v != "0.0.0.0" { return v } } return "" } // windowsVersion reads the build from the registry, for the startup log. // // Not curiosity: "WebView2 will not install either" is a statement about the // machine, not about OpsLog, and the build number is the first thing anyone // diagnosing that will ask for. func windowsVersion() string { k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) if err != nil { return "unknown" } defer k.Close() name, _, _ := k.GetStringValue("ProductName") build, _, _ := k.GetStringValue("CurrentBuild") ubr, _, _ := k.GetIntegerValue("UBR") display, _, _ := k.GetStringValue("DisplayVersion") return fmt.Sprintf("%s %s build %s.%d", name, display, build, ubr) } // edgePolicyNotes reports the administrative policies applied to Edge, which is // the engine WebView2 runs on. // // "Edge is blocked on this PC" is a sentence about group policy, and policy is // readable: rather than have an operator guess which rule is in the way, the // names of the values under the Edge policy key go into the startup log. It is // evidence for whoever administers that machine, in the one file we can be sure // they will be sent. // // Value NAMES only, never their contents: a policy key can hold URLs, account // names and site lists that are nobody's business here. func edgePolicyNotes() string { var found []string for _, hive := range []registry.Key{registry.LOCAL_MACHINE, registry.CURRENT_USER} { k, err := registry.OpenKey(hive, `SOFTWARE\Policies\Microsoft\Edge`, registry.QUERY_VALUE|registry.ENUMERATE_SUB_KEYS) if err != nil { continue } names, _ := k.ReadValueNames(0) subs, _ := k.ReadSubKeyNames(0) k.Close() where := "HKLM" if hive == registry.CURRENT_USER { where = "HKCU" } if len(names) > 0 || len(subs) > 0 { found = append(found, fmt.Sprintf("%s: %d policy values, %d sub-keys %v", where, len(names), len(subs), names)) } } if len(found) == 0 { return "no Edge policies set" } return strings.Join(found, " | ") } // edgeExecutionBlocked reports an Image File Execution Options entry that stops // the WebView2 process from running. // // This is what the "Edge blocker" utilities do: they register a Debugger value // under IFEO for msedge.exe and msedgewebview2.exe, which makes Windows launch // something else — usually nothing — in its place. The WebView2 runtime is // installed, it registers its version, and it never starts, which is exactly // the hang OpsLog was showing. Confirmed on a real machine: an Edge blocker was // in the way, and unblocking it fixed the launch. // // One line in the startup log instead of an afternoon. func edgeExecutionBlocked() string { const ifeo = `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\` var hits []string for _, exe := range []string{"msedge.exe", "msedgewebview2.exe"} { k, err := registry.OpenKey(registry.LOCAL_MACHINE, ifeo+exe, registry.QUERY_VALUE) if err != nil { continue } dbg, _, err := k.GetStringValue("Debugger") k.Close() if err == nil && strings.TrimSpace(dbg) != "" { hits = append(hits, fmt.Sprintf("%s → Debugger=%q", exe, dbg)) } } if len(hits) == 0 { return "" } return strings.Join(hits, " | ") }