4
4
import * as fsapi from 'fs-extra' ;
5
5
import * as path from 'path' ;
6
6
import { Event , EventEmitter } from 'vscode' ;
7
- import { Architecture } from '../../../../common/utils/platform' ;
7
+ import { traceWarning } from '../../../../common/logger' ;
8
+ import { Architecture , getEnvironmentVariable } from '../../../../common/utils/platform' ;
8
9
import {
9
10
PythonEnvInfo , PythonEnvKind , PythonReleaseLevel , PythonVersion ,
10
11
} from '../../../base/info' ;
11
12
import { parseVersion } from '../../../base/info/pythonVersion' ;
12
13
import { ILocator , IPythonEnvsIterator } from '../../../base/locator' ;
13
14
import { PythonEnvsChangedEvent } from '../../../base/watcher' ;
14
15
import { getFileInfo } from '../../../common/externalDependencies' ;
15
- import { getWindowsStoreAppsRoot , isWindowsPythonExe , isWindowsStoreEnvironment } from '../../../common/windowsUtils' ;
16
+ import { isWindowsPythonExe } from '../../../common/windowsUtils' ;
16
17
import { IEnvironmentInfoService } from '../../../info/environmentInfoService' ;
17
18
19
+ /**
20
+ * Gets path to the Windows Apps directory.
21
+ * @returns {string } : Returns path to the Windows Apps directory under
22
+ * `%LOCALAPPDATA%/Microsoft/WindowsApps`.
23
+ */
24
+ export function getWindowsStoreAppsRoot ( ) : string {
25
+ const localAppData = getEnvironmentVariable ( 'LOCALAPPDATA' ) || '' ;
26
+ return path . join ( localAppData , 'Microsoft' , 'WindowsApps' ) ;
27
+ }
28
+
29
+ /**
30
+ * Checks if a given path is under the forbidden windows store directory.
31
+ * @param {string } interpreterPath : Absolute path to the python interpreter.
32
+ * @returns {boolean } : Returns true if `interpreterPath` is under
33
+ * `%ProgramFiles%/WindowsApps`.
34
+ */
35
+ export function isForbiddenStorePath ( interpreterPath :string ) :boolean {
36
+ const programFilesStorePath = path
37
+ . join ( getEnvironmentVariable ( 'ProgramFiles' ) || 'Program Files' , 'WindowsApps' )
38
+ . normalize ( )
39
+ . toUpperCase ( ) ;
40
+ return path . normalize ( interpreterPath ) . toUpperCase ( ) . includes ( programFilesStorePath ) ;
41
+ }
42
+
43
+ /**
44
+ * Checks if the given interpreter belongs to Windows Store Python environment.
45
+ * @param interpreterPath: Absolute path to any python interpreter.
46
+ *
47
+ * Remarks:
48
+ * 1. Checking if the path includes `Microsoft\WindowsApps`, `Program Files\WindowsApps`, is
49
+ * NOT enough. In WSL, `/mnt/c/users/user/AppData/Local/Microsoft/WindowsApps` is available as a search
50
+ * path. It is possible to get a false positive for that path. So the comparison should check if the
51
+ * absolute path to 'WindowsApps' directory is present in the given interpreter path. The WSL path to
52
+ * 'WindowsApps' is not a valid path to access, Windows Store Python.
53
+ *
54
+ * 2. 'startsWith' comparison may not be right, user can provide '\\?\C:\users\' style long paths in windows.
55
+ *
56
+ * 3. A limitation of the checks here is that they don't handle 8.3 style windows paths.
57
+ * For example,
58
+ * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE`
59
+ * is the shortened form of
60
+ * `C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe`
61
+ *
62
+ * The correct way to compare these would be to always convert given paths to long path (or to short path).
63
+ * For either approach to work correctly you need actual file to exist, and accessible from the user's
64
+ * account.
65
+ *
66
+ * To convert to short path without using N-API in node would be to use this command. This is very expensive:
67
+ * `> cmd /c for %A in ("C:\Users\USER\AppData\Local\Microsoft\WindowsApps\python3.7.exe") do @echo %~sA`
68
+ * The above command will print out this:
69
+ * `C:\Users\USER\AppData\Local\MICROS~1\WINDOW~1\PYTHON~2.EXE`
70
+ *
71
+ * If we go down the N-API route, use node-ffi and either call GetShortPathNameW or GetLongPathNameW from,
72
+ * Kernel32 to convert between the two path variants.
73
+ *
74
+ */
75
+ export async function isWindowsStoreEnvironment ( interpreterPath : string ) : Promise < boolean > {
76
+ const pythonPathToCompare = path . normalize ( interpreterPath ) . toUpperCase ( ) ;
77
+ const localAppDataStorePath = path
78
+ . normalize ( getWindowsStoreAppsRoot ( ) )
79
+ . toUpperCase ( ) ;
80
+ if ( pythonPathToCompare . includes ( localAppDataStorePath ) ) {
81
+ return true ;
82
+ }
83
+
84
+ // Program Files store path is a forbidden path. Only admins and system has access this path.
85
+ // We should never have to look at this path or even execute python from this path.
86
+ if ( isForbiddenStorePath ( pythonPathToCompare ) ) {
87
+ traceWarning ( 'isWindowsStoreEnvironment called with Program Files store path.' ) ;
88
+ return true ;
89
+ }
90
+ return false ;
91
+ }
92
+
18
93
/**
19
94
* Gets paths to the Python executable under Windows Store apps.
20
95
* @returns : Returns python*.exe for the windows store app root directory.
@@ -59,7 +134,7 @@ export class WindowsStoreLocator implements ILocator {
59
134
60
135
public async resolveEnv ( env : string | PythonEnvInfo ) : Promise < PythonEnvInfo | undefined > {
61
136
const executablePath = typeof env === 'string' ? env : env . executable . filename ;
62
- if ( isWindowsStoreEnvironment ( executablePath ) ) {
137
+ if ( await isWindowsStoreEnvironment ( executablePath ) ) {
63
138
const interpreterInfo = await this . envService . getEnvironmentInfo ( executablePath ) ;
64
139
if ( interpreterInfo ) {
65
140
const data = await getFileInfo ( executablePath ) ;
@@ -68,7 +143,6 @@ export class WindowsStoreLocator implements ILocator {
68
143
...data ,
69
144
} ;
70
145
return Promise . resolve ( {
71
- id : '' ,
72
146
name : '' ,
73
147
location : '' ,
74
148
kind : this . kind ,
@@ -100,7 +174,6 @@ export class WindowsStoreLocator implements ILocator {
100
174
} ;
101
175
}
102
176
return {
103
- id : '' ,
104
177
name : '' ,
105
178
location : '' ,
106
179
kind : this . kind ,
0 commit comments