mirror of
https://github.com/dorssel/usbipd-win.git
synced 2026-11-10 22:50:48 +08:00
Refactor driver custom actions
- also fixes VBoxUSBMon logging
This commit is contained in:
34 files changed
+287
-439
No files matched your search
@@ -22,12 +22,6 @@
|
||||
*.cs text eol=crlf
|
||||
*.props text
|
||||
|
||||
*.vcxproj text
|
||||
*.filters text
|
||||
*.cpp text
|
||||
*.h text
|
||||
*.def text
|
||||
|
||||
*.wixproj text
|
||||
*.wxs text
|
||||
*.xslt text
|
||||
|
||||
@@ -59,12 +59,6 @@ jobs:
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
- name: Add msbuild to PATH
|
||||
uses: microsoft/setup-msbuild@v2
|
||||
|
||||
- name: Build (CustomActions)
|
||||
run: msbuild -p:Configuration=Release CustomActions
|
||||
|
||||
- name: Build (Installer)
|
||||
run: dotnet build --configuration Release Installer
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
set noparent
|
||||
linelength=160
|
||||
|
||||
# incompatible with pre-compiled headers
|
||||
filter=-build/include_subdir
|
||||
@@ -1,9 +0,0 @@
|
||||
; SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
;
|
||||
; SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
LIBRARY
|
||||
|
||||
EXPORTS
|
||||
InstallDrivers
|
||||
UninstallDrivers
|
||||
@@ -1,97 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<VCProjectVersion>17.0</VCProjectVersion>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<ProjectGuid>{cbc206bd-0e45-4514-b148-9035c7459268}</ProjectGuid>
|
||||
<RootNamespace>CustomActions</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>DynamicLibrary</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v143</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)'=='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 Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<GenerateManifest>false</GenerateManifest>
|
||||
<OutDir>bin\$(Platform)\$(Configuration)\</OutDir>
|
||||
<IntDir>obj\$(Platform)\$(Configuration)\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<SDLCheck>true</SDLCheck>
|
||||
<PreprocessorDefinitions>NDEBUG;CUSTOMACTIONS_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
|
||||
<LanguageStandard>stdcpp20</LanguageStandard>
|
||||
<LanguageStandard_C>stdc17</LanguageStandard_C>
|
||||
<Optimization>MinSpace</Optimization>
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<DebugInformationFormat>None</DebugInformationFormat>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableUAC>false</EnableUAC>
|
||||
<ModuleDefinitionFile>CustomActions.def</ModuleDefinitionFile>
|
||||
<SetChecksum>true</SetChecksum>
|
||||
<MinimumRequiredVersion>10.0</MinimumRequiredVersion>
|
||||
<ImportLibrary />
|
||||
<IncrementalLinkDatabaseFile />
|
||||
<ManifestFile />
|
||||
<GenerateDebugInformation>false</GenerateDebugInformation>
|
||||
<ProgramDatabaseFile />
|
||||
<LinkTimeCodeGeneration>UseLinkTimeCodeGeneration</LinkTimeCodeGeneration>
|
||||
<ProfileGuidedDatabase />
|
||||
<LinkTimeCodeGenerationObjectFile />
|
||||
<TypeLibraryResourceID />
|
||||
<AdditionalOptions>/NOIMPLIB /NOEXP %(AdditionalOptions)</AdditionalOptions>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp" />
|
||||
<ClCompile Include="actions.cpp" />
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="CustomActions.def" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<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;c++;cppm;ixx;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;h++;hm;inl;inc;ipp;xsd</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="stdafx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="dllmain.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stdafx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="actions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="CustomActions.def">
|
||||
<Filter>Source Files</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -1,90 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
using std::wstring;
|
||||
|
||||
|
||||
static void log(MSIHANDLE hInstall, wstring fmt, ...) {
|
||||
auto hRecord = MsiCreateRecord(0);
|
||||
if (!hRecord) {
|
||||
return;
|
||||
}
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
va_list ap_copy;
|
||||
va_copy(ap_copy, ap);
|
||||
size_t len = _vscwprintf(fmt.c_str(), ap_copy);
|
||||
va_end(ap_copy);
|
||||
wstring message;
|
||||
message.resize(len);
|
||||
vswprintf_s(message.data(), len + 1, fmt.c_str(), ap);
|
||||
va_end(ap);
|
||||
MsiRecordSetString(hRecord, 0, (L"CustomActions: " + message).c_str());
|
||||
MsiProcessMessage(hInstall, INSTALLMESSAGE_INFO, hRecord);
|
||||
MsiCloseHandle(hRecord);
|
||||
}
|
||||
|
||||
|
||||
static wstring get_property(MSIHANDLE hInstall, const wstring& name) {
|
||||
DWORD valueSize = 0;
|
||||
if (MsiGetProperty(hInstall, name.c_str(), (LPWSTR)L"", &valueSize) != ERROR_MORE_DATA) {
|
||||
return wstring();
|
||||
}
|
||||
++valueSize; // add NUL
|
||||
auto value = std::make_unique<WCHAR[]>(valueSize);
|
||||
if (MsiGetProperty(hInstall, name.c_str(), value.get(), &valueSize) != ERROR_SUCCESS) {
|
||||
return wstring();
|
||||
}
|
||||
return wstring(value.get(), valueSize);
|
||||
}
|
||||
|
||||
|
||||
// This action must run deferred, between InstallFiles and InstallFinalize.
|
||||
UINT __stdcall InstallDrivers(MSIHANDLE hInstall) {
|
||||
auto data = get_property(hInstall, L"CustomActionData");
|
||||
{
|
||||
// See: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/preinstalling-driver-packages
|
||||
log(hInstall, L"Installing VBoxUSB");
|
||||
if (!SetupCopyOEMInf((data + L"Drivers\\VBoxUSB.inf").c_str(), NULL, SPOST_PATH, 0, NULL, 0, NULL, NULL)) {
|
||||
log(hInstall, L"ERROR installing VBoxUSB: 0x%08x", GetLastError());
|
||||
return ERROR_INSTALL_FAILURE;
|
||||
}
|
||||
}
|
||||
{
|
||||
log(hInstall, L"Installing VBoxUSBMon");
|
||||
SC_HANDLE manager = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
|
||||
if (!manager) {
|
||||
log(hInstall, L"ERROR OpenSCManager: 0x%08x", GetLastError());
|
||||
return ERROR_INSTALL_FAILURE;
|
||||
}
|
||||
SC_HANDLE service = CreateService(manager, L"VBoxUSBMon", L"VirtualBox USB Monitor Service", GENERIC_ALL, SERVICE_KERNEL_DRIVER,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, (data + L"Drivers\\VBoxUSBMon.sys").c_str(), NULL, NULL, NULL, NULL, NULL);
|
||||
if (!service) {
|
||||
log(hInstall, L"ERROR CreateService: 0x%08x", GetLastError());
|
||||
CloseServiceHandle(manager);
|
||||
return ERROR_INSTALL_FAILURE;
|
||||
}
|
||||
CloseServiceHandle(service);
|
||||
CloseServiceHandle(manager);
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
// This action must run deferred, between InstallFiles and InstallFinalize.
|
||||
UINT __stdcall UninstallDrivers(MSIHANDLE hInstall) {
|
||||
auto data = get_property(hInstall, L"CustomActionData");
|
||||
{
|
||||
BOOL need_reboot = FALSE;
|
||||
log(hInstall, L"Uninstalling VBoxUSB");
|
||||
if (!DiUninstallDriver(NULL, (data + L"Drivers\\VBoxUSB.inf").c_str(), 0, &need_reboot)) {
|
||||
log(hInstall, L"ERROR uninstalling VBoxUSB: 0x%08x", GetLastError());
|
||||
// continue
|
||||
}
|
||||
// ignore need_reboot
|
||||
}
|
||||
return ERROR_SUCCESS;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
|
||||
switch (ul_reason_for_call) {
|
||||
case DLL_PROCESS_ATTACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
case DLL_PROCESS_DETACH:
|
||||
break;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "stdafx.h"
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdarg>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
// Target Windows 10 or newer (including Windows Server 2019) only
|
||||
#define WINVER 0x0A00
|
||||
#define _WIN32_WINNT 0x0A00
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
|
||||
// Windows Header Files
|
||||
#include <Windows.h>
|
||||
#include <Msi.h>
|
||||
#include <MsiQuery.h>
|
||||
#pragma comment(lib, "msi.lib")
|
||||
#include <Newdev.h>
|
||||
#pragma comment(lib, "newdev.lib")
|
||||
#include <Setupapi.h>
|
||||
#pragma comment(lib, "setupapi.lib")
|
||||
+12
-1
@@ -4,7 +4,10 @@ SPDX-FileCopyrightText: 2020 Frans van Dorsselaer
|
||||
|
||||
SPDX-License-Identifier: GPL-3.0-only
|
||||
-->
|
||||
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs">
|
||||
<Wix
|
||||
xmlns="http://wixtoolset.org/schemas/v4/wxs"
|
||||
xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util"
|
||||
>
|
||||
<Fragment>
|
||||
<DirectoryRef Id="APPLICATIONFOLDER">
|
||||
<Directory Id="Drivers" Name="Drivers" FileSource="$(var.DriversDir)">
|
||||
@@ -22,6 +25,14 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
</Component>
|
||||
<Component Id="VBoxUSBMon.sys" Bitness="always64">
|
||||
<File Id="VBoxUSBMon.sys" Name="VBoxUSBMon.sys" />
|
||||
<util:EventSource
|
||||
Name="VBoxUSBMon"
|
||||
Log="System"
|
||||
EventMessageFile="%SystemRoot%\System32\IoLogMsg.dll"
|
||||
SupportsInformationals="true"
|
||||
SupportsWarnings="true"
|
||||
SupportsErrors="true"
|
||||
/>
|
||||
</Component>
|
||||
</Directory>
|
||||
</DirectoryRef>
|
||||
|
||||
@@ -90,19 +90,17 @@ SPDX-License-Identifier: GPL-3.0-only
|
||||
</Component>
|
||||
</DirectoryRef>
|
||||
|
||||
<Binary Id="CustomActions" SourceFile="../CustomActions/bin/x64/Release/CustomActions.dll" />
|
||||
|
||||
<!-- This will restore the original Windows drivers for devices that were forced bound. -->
|
||||
<SetProperty Id="UnbindAll" Value=""[#usbipd.exe]" unbind --all" Sequence="execute" Before="UnbindAll" Condition="(?usbipd.exe=3) AND ($usbipd.exe=2)" />
|
||||
<CustomAction Id="UnbindAll" BinaryRef="Wix4UtilCA_X64" DllEntry="WixQuietExec" Return="ignore" Impersonate="no" Execute="deferred" />
|
||||
|
||||
<!-- This will uninstall the drivers. -->
|
||||
<SetProperty Id="UninstallDrivers" Value="[APPLICATIONFOLDER]" Sequence="execute" Before="UninstallDrivers" Condition="(?usbipd.exe=3) AND ($usbipd.exe=2)" />
|
||||
<CustomAction Id="UninstallDrivers" BinaryRef="CustomActions" DllEntry="UninstallDrivers" Impersonate="no" Execute="deferred" />
|
||||
<SetProperty Id="UninstallDrivers" Value=""[#usbipd.exe]" uninstall" Sequence="execute" Before="UninstallDrivers" Condition="(?usbipd.exe=3) AND ($usbipd.exe=2)" />
|
||||
<CustomAction Id="UninstallDrivers" BinaryRef="Wix4UtilCA_X64" DllEntry="WixQuietExec" Return="ignore" Impersonate="no" Execute="deferred" />
|
||||
|
||||
<!-- This will install the drivers. -->
|
||||
<SetProperty Id="InstallDrivers" Value="[APPLICATIONFOLDER]" Sequence="execute" Before="InstallDrivers" Condition="$usbipd.exe=3" />
|
||||
<CustomAction Id="InstallDrivers" BinaryRef="CustomActions" DllEntry="InstallDrivers" Impersonate="no" Execute="deferred" />
|
||||
<SetProperty Id="InstallDrivers" Value=""[#usbipd.exe]" install" Sequence="execute" Before="InstallDrivers" Condition="$usbipd.exe=3" />
|
||||
<CustomAction Id="InstallDrivers" BinaryRef="Wix4UtilCA_X64" DllEntry="WixQuietExec" Return="check" Impersonate="no" Execute="deferred" />
|
||||
|
||||
<!--
|
||||
This will *try to* start the service. However, unlike ServiceControl, it will not fail if it can't.
|
||||
|
||||
@@ -4,5 +4,3 @@
|
||||
|
||||
FILE_ALLOCATED_RANGE_BUFFER
|
||||
FSCTL_QUERY_ALLOCATED_RANGES
|
||||
|
||||
DEVPKEY_NAME
|
||||
@@ -10,12 +10,12 @@ abstract class ParseTestBase
|
||||
{
|
||||
internal static Mock<ICommandHandlers> CreateMock() => new(MockBehavior.Strict);
|
||||
|
||||
internal static void Test(Program.ExitCode expect, params string[] args)
|
||||
internal static void Test(ExitCode expect, params string[] args)
|
||||
{
|
||||
Test(expect, CreateMock(), args);
|
||||
}
|
||||
|
||||
internal static void Test(Program.ExitCode expect, Mock<ICommandHandlers> mock, params string[] args)
|
||||
internal static void Test(ExitCode expect, Mock<ICommandHandlers> mock, params string[] args)
|
||||
{
|
||||
Assert.AreEqual(MockBehavior.Strict, mock.Behavior);
|
||||
var exitCode = Program.Run(new TestConsole(), mock.Object, args);
|
||||
|
||||
@@ -7,8 +7,6 @@ using Usbipd.Automation;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_attach_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -7,8 +7,6 @@ using Usbipd.Automation;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_bind_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -7,8 +7,6 @@ using Usbipd.Automation;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_detach_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.CommandLine;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_license_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.CommandLine;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_list_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.CommandLine;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_server_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.CommandLine;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_state_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -7,8 +7,6 @@ using Usbipd.Automation;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_unbind_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_usbipd_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Parse_wsl_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -6,8 +6,6 @@ using System.CommandLine;
|
||||
|
||||
namespace UnitTests;
|
||||
|
||||
using ExitCode = Program.ExitCode;
|
||||
|
||||
[TestClass]
|
||||
sealed class Program_Tests
|
||||
: ParseTestBase
|
||||
|
||||
@@ -8,33 +8,12 @@ using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Text.Encodings.Web;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using Usbipd.Automation;
|
||||
using static Usbipd.ConsoleTools;
|
||||
using ExitCode = Usbipd.Program.ExitCode;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
interface ICommandHandlers
|
||||
{
|
||||
public Task<ExitCode> AttachWsl(BusId busId, bool autoAttach, string? distribution, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> AttachWsl(VidPid vidPid, bool autoAttach, string? distribution, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Bind(BusId busId, bool force, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Bind(VidPid vidPid, bool force, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Detach(BusId busId, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Detach(VidPid vidPid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> DetachAll(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> License(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> List(bool usbids, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Server(string[] args, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(BusId busId, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(Guid guid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(VidPid vidPid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> UnbindAll(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> State(IConsole console, CancellationToken cancellationToken);
|
||||
}
|
||||
|
||||
sealed class CommandHandlers : ICommandHandlers
|
||||
sealed partial class CommandHandlers : ICommandHandlers
|
||||
{
|
||||
static List<UsbDevice> GetDevicesByHardwareId(VidPid vidPid, bool connectedOnly, IConsole console)
|
||||
{
|
||||
@@ -245,72 +224,6 @@ sealed class CommandHandlers : ICommandHandlers
|
||||
return Task.FromResult(Bind(busId, force, console));
|
||||
}
|
||||
|
||||
async Task<ExitCode> ICommandHandlers.Server(string[] args, IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
// Pre-conditions that may fail due to user mistakes. Fail gracefully...
|
||||
|
||||
if (!CheckWriteAccess(console))
|
||||
{
|
||||
return ExitCode.AccessDenied;
|
||||
}
|
||||
|
||||
using var mutex = new Mutex(true, Server.SingletonMutexName, out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
console.ReportError("Another instance is already running.");
|
||||
return ExitCode.Failure;
|
||||
}
|
||||
|
||||
// From here on, the server should run without error. Any further errors (exceptions) are probably bugs...
|
||||
|
||||
using var host = Host.CreateDefaultBuilder()
|
||||
.UseWindowsService()
|
||||
.ConfigureAppConfiguration((context, builder) =>
|
||||
{
|
||||
var defaultConfig = new Dictionary<string, string?>();
|
||||
if (WindowsServiceHelpers.IsWindowsService())
|
||||
{
|
||||
// EventLog defaults to Warning, which is OK for .NET components,
|
||||
// but we want to specifically log Information from our own component.
|
||||
defaultConfig.Add($"Logging:EventLog:LogLevel:{nameof(Usbipd)}", "Information");
|
||||
}
|
||||
else
|
||||
{
|
||||
// When not running as a Windows service, do not spam the EventLog.
|
||||
defaultConfig.Add("Logging:EventLog:LogLevel:Default", "None");
|
||||
}
|
||||
// set the above as defaults
|
||||
builder.AddInMemoryCollection(defaultConfig);
|
||||
// allow overrides from the environment
|
||||
builder.AddEnvironmentVariables();
|
||||
// allow overrides from the command line
|
||||
builder.AddCommandLine(args);
|
||||
})
|
||||
.ConfigureLogging((context, logging) =>
|
||||
{
|
||||
if (!EventLog.SourceExists(Program.Product))
|
||||
{
|
||||
EventLog.CreateEventSource(Program.Product, "Application");
|
||||
}
|
||||
logging.AddEventLog(settings =>
|
||||
{
|
||||
settings.SourceName = Program.Product;
|
||||
});
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddHostedService<Server>();
|
||||
services.AddSingleton<PcapNg>();
|
||||
services.AddScoped<ClientContext>();
|
||||
services.AddScoped<ConnectedClient>();
|
||||
services.AddScoped<AttachedClient>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync(cancellationToken);
|
||||
return ExitCode.Success;
|
||||
}
|
||||
|
||||
Task<ExitCode> ICommandHandlers.Unbind(BusId busId, IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
var device = UsbDevice.GetAll().Where(d => d.BusId.HasValue && d.BusId.Value == busId).SingleOrDefault();
|
||||
@@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.CommandLine;
|
||||
using System.Runtime.InteropServices;
|
||||
using Microsoft.Win32.SafeHandles;
|
||||
using Windows.Win32;
|
||||
using Windows.Win32.Devices.DeviceAndDriverInstallation;
|
||||
using Windows.Win32.Foundation;
|
||||
using Windows.Win32.Security;
|
||||
using Windows.Win32.System.Services;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
sealed partial class CommandHandlers : ICommandHandlers
|
||||
{
|
||||
sealed class SafeScHandle : SafeHandleZeroOrMinusOneIsInvalid
|
||||
{
|
||||
public SafeScHandle(SC_HANDLE handle)
|
||||
: base(true)
|
||||
{
|
||||
SetHandle(handle);
|
||||
}
|
||||
|
||||
public static implicit operator SC_HANDLE(SafeScHandle scHandle) =>
|
||||
scHandle.IsClosed || scHandle.IsInvalid ? (SC_HANDLE)0 : (SC_HANDLE)scHandle.handle;
|
||||
|
||||
protected override bool ReleaseHandle()
|
||||
{
|
||||
return PInvoke.CloseServiceHandle((SC_HANDLE)handle);
|
||||
}
|
||||
}
|
||||
|
||||
Task<ExitCode> ICommandHandlers.Install(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
ConsoleTools.ReportInfo(console, "install helper");
|
||||
var path = Path.GetDirectoryName(Environment.ProcessPath)!;
|
||||
ConsoleTools.ReportInfo(console, $"path = {path}");
|
||||
|
||||
{
|
||||
ConsoleTools.ReportInfo(console, $"Installing VBoxUSB");
|
||||
// See: https://learn.microsoft.com/en-us/windows-hardware/drivers/install/preinstalling-driver-packages
|
||||
unsafe
|
||||
{
|
||||
fixed (char* inf = Path.Combine(path, "Drivers", "VBoxUSB.inf"))
|
||||
{
|
||||
if (!PInvoke.SetupCopyOEMInf(inf, null, OEM_SOURCE_MEDIA_TYPE.SPOST_PATH, 0, null, 0))
|
||||
{
|
||||
console.ReportError($"SetupCopyOEMInf -> {Marshal.GetLastWin32Error()}");
|
||||
return Task.FromResult(ExitCode.Failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
ConsoleTools.ReportInfo(console, $"Installing VBoxUSBMon");
|
||||
// NOTE: This cannot be done from WiX, since WiX cannot create SERVICE_KERNEL_DRIVER; removal from WiX works though.
|
||||
using var manager = new SafeScHandle(PInvoke.OpenSCManager(string.Empty, PInvoke.SERVICES_ACTIVE_DATABASE, PInvoke.SC_MANAGER_ALL_ACCESS));
|
||||
if (manager.IsInvalid)
|
||||
{
|
||||
console.ReportError($"OpenSCManager -> {Marshal.GetLastWin32Error()}");
|
||||
return Task.FromResult(ExitCode.Failure);
|
||||
}
|
||||
unsafe
|
||||
{
|
||||
using var service = new SafeScHandle(PInvoke.CreateService(manager, "VBoxUSBMon", "VirtualBox USB Monitor Service",
|
||||
(uint)GENERIC_ACCESS_RIGHTS.GENERIC_ALL, ENUM_SERVICE_TYPE.SERVICE_KERNEL_DRIVER, SERVICE_START_TYPE.SERVICE_DEMAND_START,
|
||||
SERVICE_ERROR.SERVICE_ERROR_NORMAL, Path.Combine(path, "Drivers", "VBoxUSBMon.sys"), null, null, null, null, null));
|
||||
if (service.IsInvalid)
|
||||
{
|
||||
console.ReportError($"CreateService -> {Marshal.GetLastWin32Error()}");
|
||||
return Task.FromResult(ExitCode.Failure);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(ExitCode.Success);
|
||||
}
|
||||
|
||||
Task<ExitCode> ICommandHandlers.Uninstall(IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
ConsoleTools.ReportInfo(console, $"uninstall helper");
|
||||
var path = Path.GetDirectoryName(Environment.ProcessPath)!;
|
||||
ConsoleTools.ReportInfo(console, $"path = {path}");
|
||||
|
||||
var success = true;
|
||||
|
||||
{
|
||||
ConsoleTools.ReportInfo(console, $"Uninstalling VBoxUSB");
|
||||
unsafe
|
||||
{
|
||||
BOOL needReboot;
|
||||
if (!PInvoke.DiUninstallDriver(HWND.Null, Path.Combine(path, "Drivers", "VBoxUSB.inf"), 0, &needReboot))
|
||||
{
|
||||
console.ReportError($"DiUninstallDriver -> {Marshal.GetLastWin32Error()}");
|
||||
success = false;
|
||||
// continue
|
||||
}
|
||||
// ignore needReboot
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult(success ? ExitCode.Success : ExitCode.Failure);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2022 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.CommandLine;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Extensions.Hosting.WindowsServices;
|
||||
using static Usbipd.ConsoleTools;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
sealed partial class CommandHandlers : ICommandHandlers
|
||||
{
|
||||
async Task<ExitCode> ICommandHandlers.Server(string[] args, IConsole console, CancellationToken cancellationToken)
|
||||
{
|
||||
// Pre-conditions that may fail due to user mistakes. Fail gracefully...
|
||||
|
||||
if (!CheckWriteAccess(console))
|
||||
{
|
||||
return ExitCode.AccessDenied;
|
||||
}
|
||||
|
||||
using var mutex = new Mutex(true, Server.SingletonMutexName, out var createdNew);
|
||||
if (!createdNew)
|
||||
{
|
||||
console.ReportError("Another instance is already running.");
|
||||
return ExitCode.Failure;
|
||||
}
|
||||
|
||||
// From here on, the server should run without error. Any further errors (exceptions) are probably bugs...
|
||||
|
||||
using var host = Host.CreateDefaultBuilder()
|
||||
.UseWindowsService()
|
||||
.ConfigureAppConfiguration((context, builder) =>
|
||||
{
|
||||
var defaultConfig = new Dictionary<string, string?>();
|
||||
if (WindowsServiceHelpers.IsWindowsService())
|
||||
{
|
||||
// EventLog defaults to Warning, which is OK for .NET components,
|
||||
// but we want to specifically log Information from our own component.
|
||||
defaultConfig.Add($"Logging:EventLog:LogLevel:{nameof(Usbipd)}", "Information");
|
||||
}
|
||||
else
|
||||
{
|
||||
// When not running as a Windows service, do not spam the EventLog.
|
||||
defaultConfig.Add("Logging:EventLog:LogLevel:Default", "None");
|
||||
}
|
||||
// set the above as defaults
|
||||
builder.AddInMemoryCollection(defaultConfig);
|
||||
// allow overrides from the environment
|
||||
builder.AddEnvironmentVariables();
|
||||
// allow overrides from the command line
|
||||
builder.AddCommandLine(args);
|
||||
})
|
||||
.ConfigureLogging((context, logging) =>
|
||||
{
|
||||
if (!EventLog.SourceExists(Program.Product))
|
||||
{
|
||||
EventLog.CreateEventSource(Program.Product, "Application");
|
||||
}
|
||||
logging.AddEventLog(settings =>
|
||||
{
|
||||
settings.SourceName = Program.Product;
|
||||
});
|
||||
})
|
||||
.ConfigureServices((hostContext, services) =>
|
||||
{
|
||||
services.AddHostedService<Server>();
|
||||
services.AddSingleton<PcapNg>();
|
||||
services.AddScoped<ClientContext>();
|
||||
services.AddScoped<ConnectedClient>();
|
||||
services.AddScoped<AttachedClient>();
|
||||
})
|
||||
.Build();
|
||||
|
||||
await host.RunAsync(cancellationToken);
|
||||
return ExitCode.Success;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
public enum ExitCode
|
||||
{
|
||||
Success = 0,
|
||||
Failure = 1,
|
||||
ParseError = 2,
|
||||
AccessDenied = 3,
|
||||
Canceled = 4,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-FileCopyrightText: 2024 Frans van Dorsselaer
|
||||
//
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
using System.CommandLine;
|
||||
using Usbipd.Automation;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
interface ICommandHandlers
|
||||
{
|
||||
public Task<ExitCode> AttachWsl(BusId busId, bool autoAttach, string? distribution, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> AttachWsl(VidPid vidPid, bool autoAttach, string? distribution, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Bind(BusId busId, bool force, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Bind(VidPid vidPid, bool force, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Detach(BusId busId, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Detach(VidPid vidPid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> DetachAll(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> License(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> List(bool usbids, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Server(string[] args, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(BusId busId, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(Guid guid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Unbind(VidPid vidPid, IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> UnbindAll(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> State(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Install(IConsole console, CancellationToken cancellationToken);
|
||||
public Task<ExitCode> Uninstall(IConsole console, CancellationToken cancellationToken);
|
||||
}
|
||||
@@ -24,15 +24,20 @@ CM_SETUP_DEVNODE_READY
|
||||
MAX_PATH
|
||||
|
||||
AdjustTokenPrivileges
|
||||
CloseServiceHandle
|
||||
CreateFile
|
||||
CreateService
|
||||
DeviceIoControl
|
||||
DiInstallDevice
|
||||
DiUninstallDriver
|
||||
GetCurrentProcess
|
||||
LookupPrivilegeValue
|
||||
OpenProcessToken
|
||||
OpenSCManager
|
||||
RegDeleteKey
|
||||
RegNotifyChangeKeyValue
|
||||
SetEvent
|
||||
SetupCopyOEMInf
|
||||
SetupDiBuildDriverInfoList
|
||||
SetupDiCreateDeviceInfoList
|
||||
SetupDiEnumDriverInfo
|
||||
@@ -97,8 +102,12 @@ FILE_DEVICE_UNKNOWN
|
||||
METHOD_BUFFERED
|
||||
|
||||
FILE_ACCESS_RIGHTS
|
||||
GENERIC_ACCESS_RIGHTS
|
||||
|
||||
FILE_WRITE_ACCESS
|
||||
SC_MANAGER_ALL_ACCESS
|
||||
|
||||
SERVICES_ACTIVE_DATABASE
|
||||
|
||||
TOKEN_ELEVATION_TYPE
|
||||
|
||||
|
||||
+30
-9
@@ -22,15 +22,6 @@ static class Program
|
||||
public static readonly string Copyright = Assembly.GetExecutingAssembly().GetCustomAttribute<AssemblyCopyrightAttribute>()!.Copyright;
|
||||
public static readonly string ApplicationName = Path.GetFileName(Process.GetCurrentProcess().ProcessName);
|
||||
|
||||
public enum ExitCode
|
||||
{
|
||||
Success = 0,
|
||||
Failure = 1,
|
||||
ParseError = 2,
|
||||
AccessDenied = 3,
|
||||
Canceled = 4,
|
||||
};
|
||||
|
||||
static BusId ParseCompatibleBusId(ArgumentResult argumentResult)
|
||||
{
|
||||
if (!BusId.TryParse(argumentResult.Tokens[0].Value, out var busId) || busId.IsIncompatibleHub)
|
||||
@@ -569,6 +560,36 @@ static class Program
|
||||
});
|
||||
rootCommand.AddCommand(wslCommand);
|
||||
}
|
||||
{
|
||||
//
|
||||
// install
|
||||
//
|
||||
var installCommand = new Command("install")
|
||||
{
|
||||
IsHidden = true,
|
||||
};
|
||||
installCommand.SetHandler(async invocationContext =>
|
||||
{
|
||||
invocationContext.ExitCode = (int)
|
||||
await commandHandlers.Install(invocationContext.Console, invocationContext.GetCancellationToken());
|
||||
});
|
||||
rootCommand.AddCommand(installCommand);
|
||||
}
|
||||
{
|
||||
//
|
||||
// uninstall
|
||||
//
|
||||
var uninstallCommand = new Command("uninstall")
|
||||
{
|
||||
IsHidden = true,
|
||||
};
|
||||
uninstallCommand.SetHandler(async invocationContext =>
|
||||
{
|
||||
invocationContext.ExitCode = (int)
|
||||
await commandHandlers.Uninstall(invocationContext.Console, invocationContext.GetCancellationToken());
|
||||
});
|
||||
rootCommand.AddCommand(uninstallCommand);
|
||||
}
|
||||
|
||||
// Same as UseDefaults() minus exception handling.
|
||||
var commandLine = new CommandLineBuilder(rootCommand)
|
||||
|
||||
@@ -13,7 +13,6 @@ using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using Usbipd.Automation;
|
||||
using static Usbipd.Program;
|
||||
|
||||
namespace Usbipd;
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ Project("{B7DD6F7E-DEF8-4E67-B5B7-07EF123DB6F0}") = "Installer", "Installer\Inst
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{3C3ED2C8-54D0-48D4-8BF4-78220D4A9E20} = {3C3ED2C8-54D0-48D4-8BF4-78220D4A9E20}
|
||||
{5CE75792-6F4F-4B16-83E6-29052C6E0D8D} = {5CE75792-6F4F-4B16-83E6-29052C6E0D8D}
|
||||
{CBC206BD-0E45-4514-B148-9035C7459268} = {CBC206BD-0E45-4514-B148-9035C7459268}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnitTests", "UnitTests\UnitTests.csproj", "{03DA15B4-7F40-4EF3-814B-9050C7A671B5}"
|
||||
@@ -36,8 +35,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Usbipd.Automation", "Usbipd
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UsbIds", "UsbIds\UsbIds.csproj", "{FC600539-9D82-4822-B2A0-7157D166A444}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CustomActions", "CustomActions\CustomActions.vcxproj", "{CBC206BD-0E45-4514-B148-9035C7459268}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|x64 = Debug|x64
|
||||
@@ -66,9 +63,6 @@ Global
|
||||
{FC600539-9D82-4822-B2A0-7157D166A444}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{FC600539-9D82-4822-B2A0-7157D166A444}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{FC600539-9D82-4822-B2A0-7157D166A444}.Release|x64.Build.0 = Release|Any CPU
|
||||
{CBC206BD-0E45-4514-B148-9035C7459268}.Debug|x64.ActiveCfg = Release|x64
|
||||
{CBC206BD-0E45-4514-B148-9035C7459268}.Debug|x64.Build.0 = Release|x64
|
||||
{CBC206BD-0E45-4514-B148-9035C7459268}.Release|x64.ActiveCfg = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
Reference in new issue
Block a user