@@ -41,9 +41,26 @@ type EnsureProgress struct {
4141 Total int64
4242}
4343
44+ type vcRuntimeRegistryState struct {
45+ Installed uint64
46+ HasInstalled bool
47+ Version string
48+ Major uint64
49+ HasMajor bool
50+ }
51+
4452var (
45- mu sync.Mutex
46- ensuring bool
53+ mu sync.Mutex
54+ ensuring bool
55+ vcRuntimeMismatchLogOnce sync.Once
56+ vcRuntimeRegistryPaths = []string {
57+ `SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64` ,
58+ `SOFTWARE\Wow6432Node\Microsoft\VisualStudio\14.0\VC\Runtimes\x64` ,
59+ }
60+ vcRuntimeUninstallKeyPaths = []string {
61+ `SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` ,
62+ `SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall` ,
63+ }
4764)
4865
4966//go:embed vcruntime140_1.dll
@@ -64,18 +81,64 @@ func fileSHA256(p string) ([]byte, error) {
6481 return h .Sum (nil ), nil
6582}
6683
67- func hasVCMinimumRuntime (path string ) bool {
84+ func readVcRuntimeRegistryState (path string ) ( vcRuntimeRegistryState , bool ) {
6885 key , err := winreg .OpenKey (winreg .LOCAL_MACHINE , path , winreg .READ )
6986 if err != nil {
87+ return vcRuntimeRegistryState {}, false
88+ }
89+ defer key .Close ()
90+
91+ state := vcRuntimeRegistryState {}
92+ if installed , _ , err := key .GetIntegerValue ("Installed" ); err == nil {
93+ state .Installed = installed
94+ state .HasInstalled = true
95+ }
96+ if version , _ , err := key .GetStringValue ("Version" ); err == nil {
97+ state .Version = strings .TrimSpace (version )
98+ }
99+ if major , _ , err := key .GetIntegerValue ("Major" ); err == nil {
100+ state .Major = major
101+ state .HasMajor = true
102+ }
103+ return state , true
104+ }
105+
106+ func isVcRuntimeRegistryStateInstalled (state vcRuntimeRegistryState ) bool {
107+ if ! state .HasInstalled || state .Installed != 1 {
70108 return false
71109 }
110+ if strings .TrimSpace (state .Version ) != "" {
111+ return true
112+ }
113+ return state .HasMajor && state .Major >= 14
114+ }
115+
116+ func hasInstalledVcRuntime (paths []string , readState func (string ) (vcRuntimeRegistryState , bool )) bool {
117+ for _ , path := range paths {
118+ state , ok := readState (path )
119+ if ! ok {
120+ continue
121+ }
122+ if isVcRuntimeRegistryStateInstalled (state ) {
123+ return true
124+ }
125+ }
126+ return false
127+ }
128+
129+ func readVCUninstallDisplayNames (path string ) ([]string , error ) {
130+ key , err := winreg .OpenKey (winreg .LOCAL_MACHINE , path , winreg .READ )
131+ if err != nil {
132+ return nil , err
133+ }
72134 defer key .Close ()
73135
74136 names , err := key .ReadSubKeyNames (- 1 )
75137 if err != nil {
76- return false
138+ return nil , err
77139 }
78140
141+ displayNames := make ([]string , 0 , len (names ))
79142 for _ , name := range names {
80143 sub , err := winreg .OpenKey (key , name , winreg .QUERY_VALUE )
81144 if err != nil {
@@ -86,21 +149,54 @@ func hasVCMinimumRuntime(path string) bool {
86149 if err != nil {
87150 continue
88151 }
152+ displayNames = append (displayNames , displayName )
153+ }
154+ return displayNames , nil
155+ }
89156
157+ func hasVCUninstallEvidenceFromDisplayNames (displayNames []string ) bool {
158+ for _ , displayName := range displayNames {
90159 l := strings .ToLower (strings .TrimSpace (displayName ))
91- if strings .Contains (l , "visual c++" ) &&
92- strings .Contains (l , "x64" ) &&
93- strings .Contains (l , "2022" ) &&
94- strings .Contains (l , "minimum runtime" ) {
160+ if ! strings .Contains (l , "visual c++" ) || ! strings .Contains (l , "x64" ) {
161+ continue
162+ }
163+ if ! strings .Contains (l , "redistributable" ) && ! strings .Contains (l , "runtime" ) {
164+ continue
165+ }
166+ if strings .Contains (l , "2015" ) ||
167+ strings .Contains (l , "2017" ) ||
168+ strings .Contains (l , "2019" ) ||
169+ strings .Contains (l , "2022" ) ||
170+ strings .Contains (l , "v14" ) {
171+ return true
172+ }
173+ }
174+ return false
175+ }
176+
177+ func hasVCUninstallEvidence (readDisplayNames func (string ) ([]string , error )) bool {
178+ for _ , path := range vcRuntimeUninstallKeyPaths {
179+ displayNames , err := readDisplayNames (path )
180+ if err != nil {
181+ continue
182+ }
183+ if hasVCUninstallEvidenceFromDisplayNames (displayNames ) {
95184 return true
96185 }
97186 }
98187 return false
99188}
100189
101190func IsInstalled () bool {
102- return hasVCMinimumRuntime (`SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall` ) ||
103- hasVCMinimumRuntime (`SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall` )
191+ if hasInstalledVcRuntime (vcRuntimeRegistryPaths , readVcRuntimeRegistryState ) {
192+ return true
193+ }
194+ if hasVCUninstallEvidence (readVCUninstallDisplayNames ) {
195+ vcRuntimeMismatchLogOnce .Do (func () {
196+ log .Println ("vcruntime: found Visual C++ uninstall entries but official VC runtime registry keys are missing" )
197+ })
198+ }
199+ return false
104200}
105201
106202func EnsureInteractive (ctx context.Context ) {
0 commit comments