diff --git a/bin/win/AppxReparse.exe b/bin/win/AppxReparse.exe deleted file mode 100644 index c3e84982e0fe..000000000000 Binary files a/bin/win/AppxReparse.exe and /dev/null differ diff --git a/native/WinElevator/AppxReparseTool/AppxReparse.rc b/native/WinElevator/AppxReparseTool/AppxReparse.rc deleted file mode 100644 index e74e559e5e87..000000000000 Binary files a/native/WinElevator/AppxReparseTool/AppxReparse.rc and /dev/null differ diff --git a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj b/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj deleted file mode 100644 index 2765e9b92991..000000000000 --- a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj +++ /dev/null @@ -1,145 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 15.0 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42} - AppxReparseTool - 10.0.18362.0 - AppxReparse - - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - Application - true - v141 - Unicode - - - Application - false - v141 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - - Level3 - MaxSpeed - true - true - true - true - Speed - - - Console - true - true - - - - - Level3 - Disabled - true - true - - - Console - - - - - Level3 - Disabled - true - true - - - Console - - - - - Level3 - MaxSpeed - true - true - true - true - - - Console - true - true - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.filters b/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.filters deleted file mode 100644 index d3f668cf84d4..000000000000 --- a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.filters +++ /dev/null @@ -1,40 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Header Files - - - - - Resource Files - - - - - Resource Files - - - - - - \ No newline at end of file diff --git a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.user b/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.user deleted file mode 100644 index 6e2aec7aefc8..000000000000 --- a/native/WinElevator/AppxReparseTool/AppxReparseTool.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/native/WinElevator/AppxReparseTool/README.txt b/native/WinElevator/AppxReparseTool/README.txt deleted file mode 100644 index f170bbb0d26c..000000000000 --- a/native/WinElevator/AppxReparseTool/README.txt +++ /dev/null @@ -1,19 +0,0 @@ -AppX apps are installed in "C:\Program Files\WindowsApps\". -You can't run them directly. Instead, you must use reparse point from -"%LOCALAPPDATA%\Microsoft\WindowsApps" (this folder is under the %PATH%) - -Reparse point is the special structure on NTFS level that stores "reparse tag" (type) and some type-specific data. -When a user accesses such files, Windows redirects the request to the appropriate target. -So, files in "%LOCALAPPDATA%\Microsoft\WindowsApps" are reparse points to AppX apps, and AppX can only be launched via them. - -But for Python, there can be a reparse point that points to Windows store, so Store is opened when Python is not installed. -There is no official way to tell if "python.exe" points to AppX python or AppX "Windows Store". - -MS provides API (via DeviceIOControl) to read reparse point structure. -There is also a reparse point tag for "AppX link" in SDK. -Reparse data is undocumented, but it is just an array of wide chars with some unprintable chars at the beginning. - -This tool reads reparse point info and tries to fetch AppX name, so we can see if it points to Store or not. -See https://youtrack.jetbrains.com/issue/PY-43082 - -Output is unicode 16-LE diff --git a/native/WinElevator/AppxReparseTool/appxreparse.c b/native/WinElevator/AppxReparseTool/appxreparse.c deleted file mode 100644 index 9ba4a859218e..000000000000 --- a/native/WinElevator/AppxReparseTool/appxreparse.c +++ /dev/null @@ -1,93 +0,0 @@ -#include -#include -#include - -// Accepts path to AppX reparse point in "%LOCALAPPDATA%\Microsoft\WindowsApps" -// Returns name of AppX or error if wrong reparse point - -typedef struct -{ - ULONG ReparseTag; - USHORT ReparseDataLength; - USHORT Reserved; - wchar_t DATA[1024]; //1024 wide chars should be enough for AppX name -} REPARSE_DATA_BUFFER; - -static DWORD _ProcessError(const wchar_t* place) -{ - const DWORD error = GetLastError(); - fwprintf(stderr, L"%ls: error %ld", place, error); - return error; -} - -int wmain(int argc, wchar_t* argv[], wchar_t* envp[]) -{ - if (argc != 2) - { - fwprintf(stderr, L"Provide path to app"); - return -1; - } - const wchar_t* path = argv[1]; - - HANDLE file = CreateFileW( - path, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT, - NULL); - if (file == INVALID_HANDLE_VALUE) - { - return _ProcessError(L"CreateFile"); - } - - REPARSE_DATA_BUFFER buffer; - ZeroMemory(&buffer, sizeof(buffer)); - DWORD numOfBytes = 0; - - if (!DeviceIoControl( - file, - FSCTL_GET_REPARSE_POINT, - NULL, - 0, - &buffer, - sizeof(buffer), - &numOfBytes, - NULL - )) - { - CloseHandle(file); - return _ProcessError(L"DeviceIoCtl"); - } - - if (numOfBytes == 0 || buffer.ReparseTag != IO_REPARSE_TAG_APPEXECLINK) - { - fwprintf(stderr, L"Not a reparse point"); - CloseHandle(file); - return -1; - } - - - const WCHAR* textStart = buffer.DATA; - // Data consists of several unprintable (<32) chars following human-readable name of AppX - // Search for first printable (>32) char. - USHORT firstPrintableChar; - const USHORT maxChars = buffer.ReparseDataLength / sizeof(WCHAR); - for (firstPrintableChar = 0; (!iswprint(*textStart)) && firstPrintableChar < maxChars; firstPrintableChar++, - textStart++) - { - } - if (firstPrintableChar == maxChars) - { - fwprintf(stderr, L"No printable chars in data"); - CloseHandle(file); - return -1; - } - //stdlib wprintf will convert chars to 1-byte charset, which may break non-ascii chars (if any) - // we print wide chars (16 bit LE) directly to stdout - const DWORD textLenInBytes = wcsnlen_s(textStart, maxChars - firstPrintableChar) * sizeof(WCHAR); - WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), textStart, textLenInBytes, &numOfBytes, NULL); - CloseHandle(file); - return 0; -} diff --git a/native/WinElevator/AppxReparseTool/resource.h b/native/WinElevator/AppxReparseTool/resource.h deleted file mode 100644 index d083e0c6a168..000000000000 --- a/native/WinElevator/AppxReparseTool/resource.h +++ /dev/null @@ -1,16 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by AppxReparse.rc -// -#define IDI_ICON1 101 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 102 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/native/WinElevator/Elevator.sln b/native/WinElevator/Elevator.sln index 4d663dc7a2b0..14b657754efc 100644 --- a/native/WinElevator/Elevator.sln +++ b/native/WinElevator/Elevator.sln @@ -18,8 +18,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution README.txt = README.txt EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppxReparseTool", "AppxReparseTool\AppxReparseTool.vcxproj", "{FFD92D09-1024-48E0-B3AB-DA83171B8F42}" -EndProject Global GlobalSection(SharedMSBuildProjectFiles) = preSolution elevShared\elevShared.vcxitems*{45d41acc-2c3c-43d2-bc10-02aa73ffc7c7}*SharedItemsImports = 9 @@ -49,14 +47,6 @@ Global {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x64.Build.0 = Release|x64 {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x86.ActiveCfg = Release|Win32 {9D71A73C-2570-488A-958B-A126F4EEECB7}.Release|x86.Build.0 = Release|Win32 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Debug|x64.ActiveCfg = Debug|x64 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Debug|x64.Build.0 = Debug|x64 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Debug|x86.ActiveCfg = Debug|Win32 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Debug|x86.Build.0 = Debug|Win32 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Release|x64.ActiveCfg = Release|x64 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Release|x64.Build.0 = Release|x64 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Release|x86.ActiveCfg = Release|Win32 - {FFD92D09-1024-48E0-B3AB-DA83171B8F42}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/native/WinElevator/README.txt b/native/WinElevator/README.txt index 8f08da06121f..13d6f68240c2 100644 --- a/native/WinElevator/README.txt +++ b/native/WinElevator/README.txt @@ -1,6 +1,3 @@ -This project consists of two tools: "launcher and elevator" and "appxreparse". - - Launcher and elevator: With UAC enabled, even an administrator has a limited access token and can't modify some folders like "Program Files". @@ -20,14 +17,12 @@ But if std(out|err|in) are redirected to files or pipes, attaching to a console In this case, the launcher creates named pipes, the elevator connects to them and provides their handlers as handlers for the newly created process. The launcher then creates threads to read/write them to the console. -For "AppxReparse" see README inside the project. ------- How to build. ToolChain: VisualStudio 2017 SDK: 8.1 (for "launcher and elevator") -Win10 10.0.18362 (for "appx reparse") You may open .sln from Visual Studio or use msbuild from VS command prompt: msbuild Elevator.sln /p:Configuration=release /property:Platform=x86 @@ -56,5 +51,5 @@ Check no error -------- How to check errors. -Both tools report errors as error codes, stderr and event log under "Application". +Tool reports errors as error codes, stderr and event log under "Application". No event ids are registered, but you can check eventId and find an appropriate ReportEvent call. diff --git a/python/intellij.python.community.impl.iml b/python/intellij.python.community.impl.iml index e9515070aabe..96e11a8aa9b7 100644 --- a/python/intellij.python.community.impl.iml +++ b/python/intellij.python.community.impl.iml @@ -78,5 +78,6 @@ + \ No newline at end of file diff --git a/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt b/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt index a4d280b4f331..2d216a28babc 100644 --- a/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt +++ b/python/src/com/jetbrains/python/sdk/flavors/WinAppxTools.kt @@ -1,11 +1,16 @@ // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.python.sdk.flavors -import com.intellij.openapi.application.PathManager +import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.util.SystemInfo +import com.sun.jna.platform.win32.Kernel32 +import com.sun.jna.platform.win32.Kernel32.* +import com.sun.jna.platform.win32.Ntifs +import com.sun.jna.platform.win32.WinioctlUtil +import com.sun.jna.ptr.IntByReference import java.io.File import java.io.FilenameFilter -import java.util.concurrent.TimeUnit +import java.nio.ByteBuffer /** * AppX packages installed to AppX volume (see ``Get-AppxDefaultVolume``, ``Get-AppxPackage``). @@ -18,7 +23,7 @@ import java.util.concurrent.TimeUnit * But when executed, they are processed by NTFS filter and redirected to their real location in AppX volume. * * There may be ``python.exe`` there, but it may point to Windows Store (so it can be installed when accessed) or to the real python. - * There is no Java API to see reparse point destination, so we use native app (See ``appxreparse.c`` and README in the same folder). + * There is no Java API to see reparse point destination, so we use JNA. * This tool returns AppX name (either ``PythonSoftwareFoundation...`` or ``DesktopAppInstaller..``). * We use it to check if ``python.exe`` is real python or WindowsStore mock. */ @@ -42,23 +47,19 @@ fun getAppxFiles(expectedProduct: String, filePattern: Regex): Collection ?.filterKeys { expectedProduct in it } ?.values ?: emptyList() + /** - * If file is AppX reparse point link -- return it's product name + * If file is AppX reparse point link -- return its product name */ val File.appxProduct: String? get() { if (parentFile?.equals(userAppxFolder) != true) return null - val reparseTool = PathManager.findBinFile("AppxReparse.exe")!! - // Intellij API prohibits running external processes under Read action or on EDT, so we use java api as it is done for registry - val process = Runtime.getRuntime().exec(arrayOf(reparseTool.toFile().absolutePath, absolutePath)) - // It is much faster in most cases, but since this code may run under EDT we limit it - if (!process.waitFor(1, TimeUnit.SECONDS)) return null - if (process.exitValue() != 0) return null - // appxreparse outputs wide chars (2 bytes), they are LE on Intel - val output = process.inputStream.readBytes().toString(Charsets.UTF_16LE) - return if (storeMarker !in output) output else null + return getAppxTag(absolutePath)?.let { + if (storeMarker !in it) it else null + } } + /** * Path to ``%LOCALAPPDATA%\Microsoft\WindowsApps`` */ @@ -72,3 +73,71 @@ private val userAppxFolder = if (appsPath.exists()) appsPath else null } } + + +// https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-fscc/c8e77b37-3909-4fe6-a4ea-2b9d423b1ee4 +private const val IO_REPARSE_TAG_APPEXECLINK = 0x8000001B + +/** + * AppX apps are installed in "C:\Program Files\WindowsApps\". +You can't run them directly. Instead, you must use reparse point from +"%LOCALAPPDATA%\Microsoft\WindowsApps" (this folder is under the %PATH%) + +Reparse point is the special structure on NTFS level that stores "reparse tag" (type) and some type-specific data. +When a user accesses such files, Windows redirects the request to the appropriate target. +So, files in "%LOCALAPPDATA%\Microsoft\WindowsApps" are reparse points to AppX apps, and AppX can only be launched via them. + +But for Python, there can be a reparse point that points to Windows store, so Store is opened when Python is not installed. +There is no official way to tell if "python.exe" points to AppX python or AppX "Windows Store". + +MS provides API (via DeviceIOControl) to read reparse point structure. +There is also a reparse point tag for "AppX link" in SDK. +Reparse data is undocumented, but it is just an array of wide chars with some unprintable chars at the beginning. + +This tool reads reparse point info and tries to fetch AppX name, so we can see if it points to Store or not. +See https://youtrack.jetbrains.com/issue/PY-43082 + +Output is unicode 16-LE + */ +private fun getAppxTag(path: String): String? { + if (!SystemInfo.isWin10OrNewer) return null + val kernel = INSTANCE + val logger = Logger.getInstance(Kernel32::class.java) + val file = kernel.CreateFile(path, GENERIC_READ, FILE_SHARE_READ, null, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT, null) + if (file == INVALID_HANDLE_VALUE) { + logger.warn("Invalid handle for $path") + return null + } + val buffer = Ntifs.REPARSE_DATA_BUFFER() + val bytesRead = IntByReference() + if (!kernel.DeviceIoControl(file, WinioctlUtil.FSCTL_GET_REPARSE_POINT, null, 0, buffer.pointer, buffer.size(), bytesRead, null)) { + logger.warn("DeviceIoControl error ${kernel.GetLastError()}") + return null + } + if (bytesRead.value < 1) { + logger.warn("0 bytes read") + return null + } + buffer.read() + if (buffer.ReparseTag != IO_REPARSE_TAG_APPEXECLINK.toInt()) { + logger.warn("Wrong tag ${buffer.ReparseTag}") + return null + } + // output is array of LE 2 bytes chars: \0\0\0[text]\0[junk] + val charBuffer = Charsets.UTF_16LE.decode(ByteBuffer.wrap(buffer.u.genericReparseBuffer.DataBuffer)) + var from = 0 + var to = 0 + var startFound = false + for ((i, char) in charBuffer.withIndex()) { + val validChar = Character.getType(char) != Character.CONTROL.toInt() + if (validChar && !startFound) { + from = i + startFound = true + } + if (!validChar && startFound) { + to = i + break + } + } + return charBuffer.substring(from, to) +} \ No newline at end of file