diff --git a/Apps/ProxmoxAutodiscoveryApp/App.cs b/Apps/ProxmoxAutodiscoveryApp/App.cs
new file mode 100644
index 00000000..304fce8c
--- /dev/null
+++ b/Apps/ProxmoxAutodiscoveryApp/App.cs
@@ -0,0 +1,357 @@
+/*
+Technitium DNS Server
+Copyright (C) 2025 Shreyas Zare (shreyas@technitium.com)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+*/
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Net;
+using System.Net.Sockets;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+using DnsServerCore.ApplicationCommon;
+using TechnitiumLibrary.Net.Dns;
+using TechnitiumLibrary.Net.Dns.ResourceRecords;
+
+namespace ProxmoxAutodiscovery
+{
+ public sealed class App : IDnsApplication, IDnsAppRecordRequestHandler
+ {
+ #region variables
+
+ private static readonly JsonSerializerOptions SerializerOptions = new()
+ {
+ Converters =
+ {
+ new IpNetworkConverter()
+ }
+ };
+
+ private IDnsServer _dnsServer;
+
+ private PveService _pveService;
+ private IReadOnlyDictionary _autodiscoveryData = new Dictionary(StringComparer.OrdinalIgnoreCase);
+
+ private CancellationTokenSource _cts;
+ private Task _backgroundUpdateLoopTask;
+
+ #endregion
+
+ #region IDisposable
+
+ public void Dispose()
+ {
+ if (_cts is { IsCancellationRequested: false } && _backgroundUpdateLoopTask is { IsCompleted: false })
+ {
+ _cts.Cancel();
+ _backgroundUpdateLoopTask?.GetAwaiter().GetResult();
+ _cts.Dispose();
+ }
+ }
+
+ #endregion
+
+ #region public
+
+ public async Task InitializeAsync(IDnsServer dnsServer, string config)
+ {
+ _dnsServer = dnsServer;
+
+ var appConfig = JsonSerializer.Deserialize(config);
+ Validator.ValidateObject(appConfig, new ValidationContext(appConfig), validateAllProperties: true);
+
+ _pveService = new PveService(
+ appConfig.ProxmoxHost,
+ appConfig.AccessToken,
+ appConfig.DisableSslValidation,
+ TimeSpan.FromSeconds(appConfig.TimeoutSeconds),
+ _dnsServer.Proxy
+ );
+
+ if (_cts is { IsCancellationRequested: false } && _backgroundUpdateLoopTask is { IsCompleted: false })
+ {
+ await _cts.CancelAsync();
+ await _backgroundUpdateLoopTask;
+ _cts.Dispose();
+ }
+
+ if (appConfig.Enabled)
+ {
+ try
+ {
+ _autodiscoveryData = await _pveService.DiscoverVmsAsync(CancellationToken.None);
+ _dnsServer.WriteLog("Successfully initialized autodiscovery cache.");
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog("Error while initializing autodiscovery cache.");
+ _dnsServer.WriteLog(ex);
+ }
+
+ _cts = new CancellationTokenSource();
+ _backgroundUpdateLoopTask = BackgroundUpdateLoop(TimeSpan.FromSeconds(appConfig.UpdateIntervalSeconds));
+ }
+ }
+
+ public Task ProcessRequestAsync(
+ DnsDatagram request,
+ IPEndPoint remoteEP,
+ DnsTransportProtocol protocol,
+ bool isRecursionAllowed,
+ string zoneName,
+ string appRecordName,
+ uint appRecordTtl,
+ string appRecordData)
+ {
+ var question = request.Question[0];
+
+ if (question is not { Type: DnsResourceRecordType.A or DnsResourceRecordType.AAAA })
+ return Task.FromResult(null);
+
+ if (!TryGetHostname(question.Name, appRecordName, out var hostname))
+ return Task.FromResult(null);
+
+ if (!_autodiscoveryData.TryGetValue(hostname, out var vm))
+ return Task.FromResult(null);
+
+ var recordConfig = JsonSerializer.Deserialize(appRecordData, SerializerOptions);
+ Validator.ValidateObject(recordConfig, new ValidationContext(recordConfig), validateAllProperties: true);
+
+ if (!IsVmMatchFilters(vm, recordConfig.Type, recordConfig.Tags))
+ return Task.FromResult(null);
+
+ var isIpv6 = question.Type == DnsResourceRecordType.AAAA;
+
+ var answer = GetMatchingIps(
+ vm.Addresses,
+ recordConfig.Networks,
+ isIpv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork)
+ .Select(x => new DnsResourceRecord(
+ question.Name,
+ question.Type,
+ DnsClass.IN,
+ appRecordTtl,
+ isIpv6
+ ? new DnsAAAARecordData(x)
+ : new DnsARecordData(x)
+ )).ToList();
+
+ var data = new DnsDatagram(
+ request.Identifier,
+ true,
+ request.OPCODE,
+ true,
+ false,
+ request.RecursionDesired,
+ isRecursionAllowed,
+ false,
+ false,
+ DnsResponseCode.NoError,
+ request.Question,
+ answer: answer);
+
+ return Task.FromResult(data);
+ }
+
+ #endregion
+
+ #region private
+
+ private async Task BackgroundUpdateLoop(TimeSpan updateInterval)
+ {
+ _dnsServer.WriteLog("Starting background data update loop.");
+
+ using var pt = new PeriodicTimer(updateInterval);
+ try
+ {
+ while (await pt.WaitForNextTickAsync(_cts.Token))
+ {
+ try
+ {
+ _autodiscoveryData = await _pveService.DiscoverVmsAsync(_cts.Token);
+ }
+ catch (Exception ex)
+ {
+ _dnsServer.WriteLog("Unexpected error while updating Proxmox data.");
+ _dnsServer.WriteLog(ex);
+ }
+ }
+ }
+ catch (OperationCanceledException oce) when (oce.CancellationToken == _cts.Token)
+ {
+ // To simplify calling code, on cancellation we're just completing the task and exiting the loop
+ }
+ }
+
+ private static bool TryGetHostname(string qname, string appRecordName, out string hostname)
+ {
+ hostname = null;
+
+ var query = qname.ToLowerInvariant();
+
+ if (query.Length <= appRecordName.Length)
+ return false;
+
+ if (!query.EndsWith(appRecordName))
+ return false;
+
+ // if appRecordName is `domain.com` we expect query to be `hostname.domain.com`
+ // we already know that query ends with appRecordName, now we need to check that query ends with dot-appRecordName
+ if (query[^(appRecordName.Length + 1)] != '.')
+ return false;
+
+ hostname = qname.Substring(0, qname.Length - appRecordName.Length - 1);
+ return true;
+ }
+
+ private static bool IsVmMatchFilters(DiscoveredVm network, string type, Filter tagFilter)
+ {
+ // If type is specified, and it's not matching VM type - do not discover this host
+ if (type != null && network.Type != type)
+ return false;
+
+ // If allowed tags are specified, VM must have all tags in the list to be discovered
+ if (tagFilter.Allowed.Length > 0 && !tagFilter.Allowed.All(x => network.Tags.Contains(x)))
+ return false;
+
+ // If excluded tags are specified, VM must have no tags from the list to be discovered
+ if (tagFilter.Excluded.Length > 0 && tagFilter.Excluded.Any(x => network.Tags.Contains(x)))
+ return false;
+
+ return true;
+ }
+
+ private static IEnumerable GetMatchingIps(
+ IPAddress[] vmAddresses,
+ Filter networkFilter,
+ AddressFamily addressFamily)
+ {
+ return vmAddresses
+ // Picking only IPv4 or IPv6 addresses
+ .Where(x => x.AddressFamily == addressFamily)
+ // IP address must be in one of the allowed networks
+ .Where(ip => networkFilter.Allowed.Any(net => net.Contains(ip)))
+ // IP address must be in none of the blocked networks
+ .Where(ip => networkFilter.Excluded.All(net => !net.Contains(ip)));
+ }
+
+ #endregion
+
+ #region properties
+
+ public string Description
+ { get { return "Allows configuring autodiscovery for Proxmox QEMUs and LXCs based on a set of filters."; } }
+
+ public string ApplicationRecordDataTemplate
+ { get { return """
+ {
+ "type": "qemu",
+ "tags": {
+ "allowed": [
+ "autodiscovery"
+ ],
+ "excluded": [
+ "hidden"
+ ]
+ },
+ "networks": {
+ "allowed": [
+ "10.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "fc00::/7"
+ ],
+ "excluded": [
+ "172.17.0.0/16"
+ ]
+ }
+ }
+ """; } }
+
+ #endregion
+
+ private sealed class AppConfig
+ {
+ [JsonPropertyName("enabled")]
+ public bool Enabled { get; set; }
+
+ [Required]
+ [JsonPropertyName("proxmoxHost")]
+ public Uri ProxmoxHost { get; set; }
+
+ [JsonPropertyName("timeoutSeconds")]
+ public int TimeoutSeconds { get; set; } = 15;
+
+ [JsonPropertyName("disableSslValidation")]
+ public bool DisableSslValidation { get; set; }
+
+ [Required]
+ [JsonPropertyName("accessToken")]
+ public string AccessToken { get; set; }
+
+ [JsonPropertyName("updateIntervalSeconds")]
+ public int UpdateIntervalSeconds { get; set; } = 60;
+ }
+
+ private sealed class AppRecordConfig
+ {
+ [AllowedValues("lxc", "qemu", null)]
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [Required]
+ [JsonPropertyName("tags")]
+ public Filter Tags { get; set; }
+
+ [Required]
+ [JsonPropertyName("networks")]
+ public Filter Networks { get; set; }
+ }
+
+ private sealed class Filter
+ {
+ [Required]
+ [JsonPropertyName("allowed")]
+ public T[] Allowed { get; set; }
+
+ [Required]
+ [JsonPropertyName("excluded")]
+ public T[] Excluded { get; set; }
+ }
+
+ private sealed class IpNetworkConverter : JsonConverter
+ {
+ public override IPNetwork Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var str = reader.GetString();
+ if (!string.IsNullOrEmpty(str))
+ return IPNetwork.Parse(str);
+
+ return default;
+ }
+
+ public override void Write(Utf8JsonWriter writer, IPNetwork value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value.ToString());
+ }
+ }
+ }
+}
diff --git a/Apps/ProxmoxAutodiscoveryApp/ProxmoxAutodiscoveryApp.csproj b/Apps/ProxmoxAutodiscoveryApp/ProxmoxAutodiscoveryApp.csproj
new file mode 100644
index 00000000..5ce75fb7
--- /dev/null
+++ b/Apps/ProxmoxAutodiscoveryApp/ProxmoxAutodiscoveryApp.csproj
@@ -0,0 +1,43 @@
+
+
+
+ net9.0
+ false
+ 0.1
+ false
+ Technitium
+ Technitium DNS Server
+ Gordei Vasiliev
+ ProxmoxAutodiscoveryApp
+ ProxmoxAutodiscovery
+ https://technitium.com/dns/
+ https://github.com/TechnitiumSoftware/DnsServer
+ Allows configuring autodiscovery for Proxmox QEMUs and LXCs based on a set of filters.
+ false
+ Library
+
+
+
+
+ false
+
+
+
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.dll
+ false
+
+
+ ..\..\..\TechnitiumLibrary\bin\TechnitiumLibrary.Net.dll
+ false
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/Apps/ProxmoxAutodiscoveryApp/PveService.cs b/Apps/ProxmoxAutodiscoveryApp/PveService.cs
new file mode 100644
index 00000000..d2f45d98
--- /dev/null
+++ b/Apps/ProxmoxAutodiscoveryApp/PveService.cs
@@ -0,0 +1,202 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace ProxmoxAutodiscovery;
+
+internal sealed class PveService
+{
+ private readonly HttpClient _client;
+
+ public PveService(Uri baseUri,
+ string accessToken,
+ bool disableSslValidation,
+ TimeSpan timeout,
+ IWebProxy proxy)
+ {
+ var handler = new HttpClientHandler { Proxy = proxy };
+
+ if (disableSslValidation)
+ handler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
+
+ _client = new HttpClient(handler)
+ {
+ BaseAddress = baseUri,
+ Timeout = timeout
+ };
+
+ _client.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"PVEAPIToken={accessToken}");
+ }
+
+ public async Task> DiscoverVmsAsync(CancellationToken cancellationToken)
+ {
+ var nodes = await GetProxmoxDataAsync("api2/json/nodes", [], cancellationToken);
+
+ var results = await Task
+ .WhenAll(nodes.Select(x => GetVmNetworksAsync(x.Node, cancellationToken)));
+
+ return results
+ .SelectMany(x => x)
+ .ToDictionary(
+ x => x.Name,
+ x => x,
+ StringComparer.OrdinalIgnoreCase);
+ }
+
+ private async Task> GetVmNetworksAsync(string node, CancellationToken cancellationToken)
+ {
+ var qemus = GetQemuVmNetworksAsync(node, cancellationToken);
+ var lxcs = GetLxcVmNetworks(node, cancellationToken);
+
+ var result = await Task.WhenAll(lxcs, qemus);
+ return result.SelectMany(x => x);
+ }
+
+ private async Task> GetQemuVmNetworksAsync(string node, CancellationToken cancellationToken)
+ {
+ var result = new List();
+ var qemus = await GetProxmoxDataAsync(
+ $"api2/json/nodes/{node}/qemu",
+ [],
+ cancellationToken);
+
+ foreach (var qemu in qemus)
+ {
+ // stopped guests have no information about network interfaces
+ if (qemu.Status != "running")
+ {
+ result.Add(Map(qemu, []));
+ continue;
+ }
+
+ try
+ {
+ var agentResponse = await GetProxmoxDataAsync(
+ $"api2/json/nodes/{node}/qemu/{qemu.VmId}/agent/network-get-interfaces",
+ new QemuAgentResponse { Result = [] },
+ cancellationToken);
+ result.Add(Map(qemu, agentResponse.Result ?? []));
+ }
+ catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.InternalServerError)
+ {
+ // Proxmox returns 500 when there is something wrong with QEMU Guest Agent (it's disabled or not running)
+ // Since at this point we already called Proxmox VE API multiple times with successful results, we can
+ // treat all 500 responses here as empty interfaces list
+ result.Add(Map(qemu, []));
+ }
+ }
+
+ return result;
+ }
+
+ private async Task> GetLxcVmNetworks(string node, CancellationToken cancellationToken)
+ {
+ var lxcs = await GetProxmoxDataAsync(
+ $"api2/json/nodes/{node}/lxc",
+ [],
+ cancellationToken);
+ var result = new List(lxcs.Length);
+
+ foreach (var lxc in lxcs)
+ {
+ // stopped guests have no information about network interfaces
+ if (lxc.Status != "running")
+ {
+ result.Add(Map(lxc, []));
+ continue;
+ }
+
+ var interfaces = await GetProxmoxDataAsync(
+ $"api2/json/nodes/{node}/lxc/{lxc.VmId}/interfaces",
+ [],
+ cancellationToken);
+ result.Add(Map(lxc, interfaces));
+ }
+
+ return result;
+ }
+
+ private async Task GetProxmoxDataAsync(string url, T defaultValue, CancellationToken cancellationToken)
+ {
+ var response = await _client.GetFromJsonAsync>(url, cancellationToken);
+ return response is { Data: not null }
+ ? response.Data
+ : defaultValue;
+ }
+
+ private static DiscoveredVm Map(VmDescription vm, VmNetworkInterface[] interfaces)
+ {
+ return new DiscoveredVm(
+ Name: vm.Name,
+ Type: vm.Type,
+ Tags: vm.Tags?.Split(';') ?? [],
+ Addresses: interfaces
+ .Where(x => x.Name != "lo") // always excluding loopback interface
+ .SelectMany(x => x.IpAddresses)
+ .Select(x => IPAddress.Parse(x.Address))
+ .ToArray());
+ }
+
+ #region DTOs
+
+ private sealed class PveResponse
+ {
+ [JsonPropertyName("data")]
+ public T Data { get; set; }
+ }
+
+ private sealed class ProxmoxNode
+ {
+ [JsonPropertyName("node")]
+ public string Node { get; set; }
+ }
+
+ private sealed class VmDescription
+ {
+ [JsonPropertyName("vmid")]
+ public long VmId { get; set; }
+
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("tags")]
+ public string Tags { get; set; }
+
+ [JsonPropertyName("type")]
+ public string Type { get; set; }
+
+ [JsonPropertyName("status")]
+ public string Status { get; set; }
+ }
+
+ private sealed class QemuAgentResponse
+ {
+ [JsonPropertyName("result")]
+ public T Result { get; set; }
+ }
+
+ private sealed class VmNetworkInterface
+ {
+ [JsonPropertyName("name")]
+ public string Name { get; set; }
+
+ [JsonPropertyName("ip-addresses")]
+ public VmIpAddress[] IpAddresses { get; set; }
+ }
+
+ private sealed class VmIpAddress
+ {
+ [JsonPropertyName("ip-address")]
+ public string Address { get; set; }
+ }
+
+ #endregion
+}
+
+public sealed record DiscoveredVm(string Name, string Type, string[] Tags, IPAddress[] Addresses);
diff --git a/Apps/ProxmoxAutodiscoveryApp/README.md b/Apps/ProxmoxAutodiscoveryApp/README.md
new file mode 100644
index 00000000..c9afb21d
--- /dev/null
+++ b/Apps/ProxmoxAutodiscoveryApp/README.md
@@ -0,0 +1,121 @@
+# Proxmox Autodiscovery for Technitium DNS Server
+
+A plugin that allows query DNS server for ip addresses of Proxmox QEMUs and LXCs without needing to add A/AAAA records manually.
+
+It collects QEMU and LXC data (name, tags and network addresses) form Proxmox API and periodically refreshes it.
+
+## Features
+
+- Stores Proxmox data in memory, dns resolution requires no additional network requests.
+- Allows to filter autodiscovered guests based on tags and type.
+- Filters guests network addresses based on list of allowed networks.
+- Supports both IPv4 and IPv6.
+
+## Dns App configuration
+
+Supply a JSON configuration like the following:
+
+```json
+{
+ "enabled": false,
+ "proxmoxHost": "https://example.com:8006",
+ "timeoutSeconds": 10,
+ "disableSslValidation": false,
+ "accessToken": "user@pve!token-name=token-secret",
+ "updateIntervalSeconds": 60
+}
+```
+
+- `enabled` - enables/disables APP.
+- `proxmoxHost` - url of Proxmox API.
+- `timeoutSeconds` - configurable timeout of HTTP calls to Proxmox API.
+- `disableSslValidation` - disables SSL certificate validation of Proxmox API.
+- `accessToken` - Proxmox API access token in specified format. Read-only permissions are enough.
+- `updateIntervalSeconds` - how often app must query Proxmox API for new data.
+
+## APP record configuration
+
+Supply a JSON configuration like the following:
+
+```json
+{
+ "type": "qemu",
+ "tags": {
+ "allowed": [
+ "autodiscovery"
+ ],
+ "excluded": [
+ "hidden"
+ ]
+ },
+ "networks": {
+ "allowed": [
+ "10.0.0.0/8",
+ "172.16.0.0/12",
+ "192.168.0.0/16",
+ "fc00::/7"
+ ],
+ "excluded": [
+ ]
+ }
+}
+```
+
+- `type` - type of guests to autodiscover. Supported values are `qemu` for QEMU vms, `lxc` for LXCs and `null` for both.
+- `tags` - filter guests by tag list.
+ - `allowed` - guest must have all specified tags to be discovered. Empty list means all guests are discoverable.
+ - `excluded` - guest must have no tags from the list to be discovered. Empty list means no guests are excluded.
+- `networks` - filter returned IP addresses by networks.
+ - `allowed` - resolve only addresses belonging to any network from the list. Empty list means no IPs are discoverable.
+ - `exluded` - resolve only addresses not belonging any networks from the list. Empty list means no IPs are excluded.
+
+## Example
+
+Discover all Proxmox guests:
+
+```json
+{
+ "type": null,
+ "tags": {
+ "allowed": [],
+ "excluded": []
+ },
+ "networks": {
+ "allowed": [
+ "0.0.0.0/0",
+ "::/0"
+ ],
+ "excluded": [
+ ]
+ }
+}
+```
+
+Discover only QEMUs with `test`, `provider` tags, excluding `broken`. Resolve only IPv4 addresses in private range excluding default docker bridge:
+
+```json
+{
+ "type": "qemu",
+ "tags": {
+ "allowed": [
+ "test",
+ "provider"
+ ],
+ "excluded": [
+ "broken"
+ ]
+ },
+ "networks": {
+ "allowed": [
+ "172.16.0.0/12"
+ ],
+ "excluded": [
+ "172.17.0.0/16"
+ ]
+ }
+}
+```
+
+# Acknowledgement
+
+Thanks to [Nikita Rukavkov](https://github.com/itcaat) and [Andrew Dunham](https://github.com/andrew-d) for the reference implementations.
diff --git a/Apps/ProxmoxAutodiscoveryApp/dnsApp.config b/Apps/ProxmoxAutodiscoveryApp/dnsApp.config
new file mode 100644
index 00000000..15a72a8a
--- /dev/null
+++ b/Apps/ProxmoxAutodiscoveryApp/dnsApp.config
@@ -0,0 +1,8 @@
+{
+ "enabled": false,
+ "proxmoxHost": "https://example.com:8006",
+ "timeoutSeconds": 10,
+ "disableSslValidation": false,
+ "accessToken": "user@pve!token-name=token-secret",
+ "updateIntervalSeconds": 60
+}
\ No newline at end of file
diff --git a/DnsServer.sln b/DnsServer.sln
index 0a2a6756..fdc6af8c 100644
--- a/DnsServer.sln
+++ b/DnsServer.sln
@@ -68,8 +68,11 @@ EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QueryLogsMySqlApp", "Apps\QueryLogsMySqlApp\QueryLogsMySqlApp.csproj", "{699E2A1D-D917-4825-939E-65CDB2B16A96}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MispConnectorApp", "Apps\MispConnectorApp\MispConnectorApp.csproj", "{83C8180A-0F86-F9A0-8F41-6FD61FAC41CB}"
+EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DnsServerCore.HttpApi", "DnsServerCore.HttpApi\DnsServerCore.HttpApi.csproj", "{1A49D371-D08C-475E-B7A2-6E8ECD181FD6}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ProxmoxAutodiscoveryApp", "Apps\ProxmoxAutodiscoveryApp\ProxmoxAutodiscoveryApp.csproj", "{3CE57BF5-FE39-4B6E-BA71-325F6F15E646}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -208,6 +211,10 @@ Global
{1A49D371-D08C-475E-B7A2-6E8ECD181FD6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1A49D371-D08C-475E-B7A2-6E8ECD181FD6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1A49D371-D08C-475E-B7A2-6E8ECD181FD6}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3CE57BF5-FE39-4B6E-BA71-325F6F15E646}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3CE57BF5-FE39-4B6E-BA71-325F6F15E646}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3CE57BF5-FE39-4B6E-BA71-325F6F15E646}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3CE57BF5-FE39-4B6E-BA71-325F6F15E646}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -240,6 +247,7 @@ Global
{6F655C97-FD43-4FE1-B15A-6C783D2D91C9} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
{699E2A1D-D917-4825-939E-65CDB2B16A96} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
{83C8180A-0F86-F9A0-8F41-6FD61FAC41CB} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
+ {3CE57BF5-FE39-4B6E-BA71-325F6F15E646} = {938BF8EF-74B9-4FE0-B46F-11EBB7A4B3D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6747BB6D-2826-4356-A213-805FBCCF9201}