mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Rewrite `AppxReparse.exe` to JNA
No need to have separate tool for that GitOrigin-RevId: 3019b647cb3b9ce9c01967deace0e4fbed834302
This commit is contained in:
committed by
intellij-monorepo-bot
parent
233e2b492f
commit
b6a9f2268e
Binary file not shown.
Binary file not shown.
@@ -1,145 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>15.0</VCProjectVersion>
|
||||
<ProjectGuid>{FFD92D09-1024-48E0-B3AB-DA83171B8F42}</ProjectGuid>
|
||||
<RootNamespace>AppxReparseTool</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
|
||||
<ProjectName>AppxReparse</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup />
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="appxreparse.c" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="AppxReparse.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="..\jb.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="README.txt" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,40 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
|
||||
<Extensions>h;hh;hpp;hxx;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="appxreparse.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="AppxReparse.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="..\jb.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Text Include="README.txt" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup />
|
||||
</Project>
|
||||
@@ -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
|
||||
@@ -1,93 +0,0 @@
|
||||
#include <Windows.h>
|
||||
#include <wchar.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// 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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -78,5 +78,6 @@
|
||||
<orderEntry type="library" name="kotlinx-coroutines-jdk8" level="project" />
|
||||
<orderEntry type="library" name="jcef" level="project" />
|
||||
<orderEntry type="library" name="Velocity" level="project" />
|
||||
<orderEntry type="library" name="jna" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -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<File>
|
||||
?.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)
|
||||
}
|
||||
Reference in New Issue
Block a user