diff --git a/Python/Product/PythonTools/PythonTools.csproj b/Python/Product/PythonTools/PythonTools.csproj
index d940ad643d..c5466ab70f 100644
--- a/Python/Product/PythonTools/PythonTools.csproj
+++ b/Python/Product/PythonTools/PythonTools.csproj
@@ -114,24 +114,6 @@
-
-
-
-
- False
- $(DevEnvDir)Extensions\Microsoft\Windows Azure Tools\Common\Microsoft.VisualStudio.WindowsAzure.CommonAzureTools.Contracts.1.2.dll
- false
-
-
- True
- $(DevEnvDir)PrivateAssemblies\Microsoft.VisualStudio.Web.WindowsAzure.Contracts.dll
- $(DevEnvDir)Extensions\Microsoft\Web Tools\WindowsAzure\Microsoft.VisualStudio.Web.WindowsAzure.Contracts.dll
- $(DevEnvDir)Extensions\Microsoft\Web Tools Azure\Microsoft.VisualStudio.Web.WindowsAzure.Contracts.dll
- false
-
-
-
- False
diff --git a/Python/Product/PythonTools/PythonTools/Commands/AzureExplorerAttachDebuggerCommand.cs b/Python/Product/PythonTools/PythonTools/Commands/AzureExplorerAttachDebuggerCommand.cs
index 22c19b5a12..5310a10f3a 100644
--- a/Python/Product/PythonTools/PythonTools/Commands/AzureExplorerAttachDebuggerCommand.cs
+++ b/Python/Product/PythonTools/PythonTools/Commands/AzureExplorerAttachDebuggerCommand.cs
@@ -16,24 +16,12 @@
#if FEATURE_AZURE_REMOTE_DEBUG
using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
using System.Net;
-using System.Net.WebSockets;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-using System.Security.Cryptography.X509Certificates;
-using System.Threading.Tasks;
-using System.Windows.Forms;
-using System.Xml;
-using System.Xml.Linq;
-using Microsoft.PythonTools.Debugger.Remote;
+using Microsoft.PythonTools.Project;
+using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
-using Microsoft.VisualStudio.Web.WindowsAzure.Contracts;
-using Microsoft.VisualStudio.WindowsAzure.Authentication;
using Microsoft.VisualStudioTools;
-using Microsoft.VisualStudioTools.Project;
namespace Microsoft.PythonTools.Commands {
///
@@ -41,25 +29,9 @@ namespace Microsoft.PythonTools.Commands {
///
internal class AzureExplorerAttachDebuggerCommand : Command {
private readonly IServiceProvider _serviceProvider;
+
public AzureExplorerAttachDebuggerCommand(IServiceProvider serviceProvider) {
_serviceProvider = serviceProvider;
- // Will throw PlatformNotSupportedException on any unsupported OS (Win7 and below).
- using (new ClientWebSocket()) { }
-
- try {
- ProbeWindowsAzureWebContractsAssembly();
- } catch (FileNotFoundException) {
- throw new NotSupportedException();
- } catch (FileLoadException) {
- throw new NotSupportedException();
- } catch (TypeLoadException) {
- throw new NotSupportedException();
- }
- }
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- private static Type ProbeWindowsAzureWebContractsAssembly() {
- return typeof(IVsAzureServices);
}
public override int CommandId {
@@ -81,22 +53,46 @@ public override void DoCommand(object sender, EventArgs args) {
throw new NotSupportedException();
}
- Action> onAttach = null;
- onAttach = (attachTask) => {
- if (!attachTask.Result) {
- string msg = string.Format(
- "Could not attach to python.exe process on Azure web site at {0}.\r\n\r\n" +
- "Error retrieving websocket debug proxy information from web.config.",
- webSite.Uri);
- if (MessageBox.Show(msg, null, MessageBoxButtons.RetryCancel, MessageBoxIcon.Error) == DialogResult.Retry) {
- AttachWorker(webSite).ContinueWith(onAttach);
+ Uri debugUri;
+ if (Uri.TryCreate(webSite.Uri, "/ptvsd", out debugUri)) {
+ // Open the site's ptvsd page if it exists
+ var req = WebRequest.CreateHttp(debugUri.AbsoluteUri);
+ req.Method = "HEAD";
+ req.Accept = "text/html";
+
+ var dlgFactory = (IVsThreadedWaitDialogFactory)_serviceProvider.GetService(typeof(SVsThreadedWaitDialogFactory));
+ IVsThreadedWaitDialog2 dlg = null;
+ if (dlgFactory != null && ErrorHandler.Succeeded(dlgFactory.CreateInstance(out dlg))) {
+ if (ErrorHandler.Failed(dlg.StartWaitDialog(
+ SR.ProductName,
+ "Getting attach information from web site.",
+ null,
+ null,
+ null,
+ 1,
+ false,
+ true
+ ))) {
+ dlg = null;
+ }
+ }
+ try {
+ req.GetResponse().Close();
+ } catch (WebException) {
+ debugUri = null;
+ } finally {
+ if (dlg != null) {
+ int dummy;
+ dlg.EndWaitDialog(out dummy);
}
}
- };
+ }
- // We will need to do a bunch of async calls here, and they will deadlock if the UI thread is
- // blocked, so we can't use Wait() or Result, and have to let the task run on its own.
- AttachWorker(webSite).ContinueWith(onAttach);
+ if (debugUri != null) {
+ CommonPackage.OpenVsWebBrowser(_serviceProvider, debugUri.AbsoluteUri);
+ } else {
+ CommonPackage.OpenVsWebBrowser(_serviceProvider, "http://go.microsoft.com/fwlink/?LinkID=624026");
+ }
}
///
@@ -109,13 +105,15 @@ private AzureWebSiteInfo GetSelectedAzureWebSite() {
var shell = (IVsUIShell)_serviceProvider.GetService(typeof(SVsUIShell));
var serverExplorerToolWindowGuid = new Guid(ToolWindowGuids.ServerExplorer);
IVsWindowFrame serverExplorerFrame;
- shell.FindToolWindow(0, ref serverExplorerToolWindowGuid, out serverExplorerFrame);
- if (serverExplorerFrame == null) {
+ if (ErrorHandler.Failed(shell.FindToolWindow(0, ref serverExplorerToolWindowGuid, out serverExplorerFrame)) ||
+ serverExplorerFrame == null) {
return null;
}
object obj;
- serverExplorerFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out obj);
+ if (ErrorHandler.Failed(serverExplorerFrame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out obj))) {
+ return null;
+ }
var serverExplorerHierWnd = obj as IVsUIHierarchyWindow;
if (serverExplorerHierWnd == null) {
return null;
@@ -124,8 +122,8 @@ private AzureWebSiteInfo GetSelectedAzureWebSite() {
IntPtr hierPtr;
uint itemid;
IVsMultiItemSelect mis;
- serverExplorerHierWnd.GetCurrentSelection(out hierPtr, out itemid, out mis);
- if (hierPtr == IntPtr.Zero) {
+ if (ErrorHandler.Failed(serverExplorerHierWnd.GetCurrentSelection(out hierPtr, out itemid, out mis)) ||
+ hierPtr == IntPtr.Zero) {
return null;
}
@@ -138,14 +136,18 @@ private AzureWebSiteInfo GetSelectedAzureWebSite() {
// Get the browse object of that node - this is the object that exposes properties to show in the Properties window.
- hier.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_SelContainer, out obj);
+ if (ErrorHandler.Failed(hier.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_SelContainer, out obj))) {
+ return null;
+ }
var selCtr = obj as ISelectionContainer;
if (selCtr == null) {
return null;
}
var objs = new object[1];
- selCtr.GetObjects((uint)Microsoft.VisualStudio.Shell.Interop.Constants.GETOBJS_SELECTED, 1, objs);
+ if (ErrorHandler.Failed(selCtr.GetObjects((uint)Constants.GETOBJS_SELECTED, 1, objs))) {
+ return null;
+ }
obj = objs[0];
if (obj == null) {
return null;
@@ -191,236 +193,6 @@ private AzureWebSiteInfo GetSelectedAzureWebSite() {
return new AzureWebSiteInfo(uri, subscriptionId);
}
- private async Task AttachWorker(AzureWebSiteInfo webSite) {
- using (new WaitDialog("Azure remote debugging", "Attaching to Azure web site at " + webSite.Uri, _serviceProvider, showProgress: true)) {
- // Get path (relative to site URL) for the debugger endpoint.
- XDocument webConfig;
- try {
- webConfig = await GetWebConfig(webSite);
- } catch (WebException) {
- return false;
- } catch (IOException) {
- return false;
- } catch (XmlException) {
- return false;
- }
- if (webConfig == null) {
- return false;
- }
-
- var path =
- (from add in webConfig.Elements("configuration").Elements("system.webServer").Elements("handlers").Elements("add")
- let type = (string)add.Attribute("type")
- where type != null
- let components = type.Split(',')
- where components[0].Trim() == "Microsoft.PythonTools.Debugger.WebSocketProxy"
- select (string)add.Attribute("path")
- ).FirstOrDefault();
- if (path == null) {
- return false;
- }
-
- var secret =
- (from add in webConfig.Elements("configuration").Elements("appSettings").Elements("add")
- where (string)add.Attribute("key") == "WSGI_PTVSD_SECRET"
- select (string)add.Attribute("value")
- ).FirstOrDefault();
- if (secret == null) {
- return false;
- }
-
- try {
- AttachDebugger(new UriBuilder(webSite.Uri) { Scheme = "wss", Port = -1, Path = path, UserName = secret }.Uri);
- } catch (Exception ex) {
- // If we got to this point, the attach logic in debug engine will catch exceptions, display proper error message and
- // ask the user to retry, so the only case where we actually get here is if user canceled on error. If this is the case,
- // we don't want to pop any additional error messages, so always return true, but log the error in the Output window.
- var output = OutputWindowRedirector.GetGeneral(_serviceProvider);
- output.WriteErrorLine("Failed to attach to Azure web site: " + ex.Message);
- output.ShowAndActivate();
- }
- return true;
- }
- }
-
- ///
- /// Retrieves web.config for a given Azure web site.
- ///
- /// XML document with the contents of web.config, or null if it could not be retrieved.
- private async Task GetWebConfig(AzureWebSiteInfo webSite) {
- var publishXml = await GetPublishXml(webSite);
- if (publishXml == null) {
- return null;
- }
-
- // Get FTP publish URL and credentials from publish settings.
-
- var publishProfile = publishXml.Elements("publishData").Elements("publishProfile").FirstOrDefault(el => (string)el.Attribute("publishMethod") == "FTP");
- if (publishProfile == null) {
- return null;
- }
-
- var publishUrl = (string)publishProfile.Attribute("publishUrl");
- var userName = (string)publishProfile.Attribute("userName");
- var userPwd = (string)publishProfile.Attribute("userPWD");
- if (publishUrl == null || userName == null || userPwd == null) {
- return null;
- }
-
- // Get web.config for the site via FTP.
-
- if (!publishUrl.EndsWith("/")) {
- publishUrl += "/";
- }
- publishUrl += "web.config";
-
- Uri webConfigUri;
- if (!Uri.TryCreate(publishUrl, UriKind.Absolute, out webConfigUri)) {
- return null;
- }
-
- var request = WebRequest.Create(webConfigUri) as FtpWebRequest;
- // Check that this is actually an FTP request, in case we get some valid but weird URL back.
- if (request == null) {
- return null;
- }
- request.Credentials = new NetworkCredential(userName, userPwd);
-
- using (var response = await request.GetResponseAsync())
- using (var stream = response.GetResponseStream()) {
- // There is no XDocument.LoadAsync, but we want the networked I/O at least to be async, even if parsing is not.
- var xmlData = new MemoryStream();
- await stream.CopyToAsync(xmlData);
- xmlData.Position = 0;
- return XDocument.Load(xmlData);
- }
- }
-
- ///
- /// Retrieves the publish settings file (.pubxml) for the given Azure web site.
- ///
- /// XML document with the contents of .pubxml, or null if it could not be retrieved.
- private async Task GetPublishXml(AzureWebSiteInfo webSiteInfo) {
- // To build the publish settings request URL, we need to know subscription ID, site name, and web region to which it belongs,
- // but we only have subscription ID and the public URL of the site at this point. Use the Azure web site service to look up
- // the site from those two, and retrieve the missing info.
-
- // The code below must avoid doing anything that would result in types from the Azure Tools contract assemblies being
- // referenced in any way in signatures of any members of any classes in this assembly. Because the contract assembly can
- // be be missing (if Azure SDK is not installed), such references will cause Reflection to break, which will break MEF
- // and block all our MEF exports. The main danger is classes generated by the compiler to support constructs such as
- // lambdas and await. To that extent, the following are not allowed in the code below:
- //
- // - local variables of Azure types (become fields if captured by a lambda or used across await);
- // - lambda arguments of Azure types (become arguments on generated method), and LINQ expressions that would implicitly
- // produce such lambdas;
- // - await on a Task that has result of an Azure type (produces a field of type TaskAwaiter);
- //
- // To make it easier to verify, "var" and LINQ syntactic sugar should be avoided completely when dealing with Azure interfaces,
- // and all lambdas should have the types of arguments explicitly specified. For "await", cast the return type of any
- // Azure-type-returning async method to untyped Task first before awaiting, then use an explicit cast to Task to read Result.
- // The only mentions of Azure types in the body of the method should be in casts.
-
- object webSiteServices = _serviceProvider.GetService(typeof(IVsAzureServices));
- if (webSiteServices == null) {
- return null;
- }
-
- object webSiteService = ((IVsAzureServices)webSiteServices).GetAzureWebSitesService();
- if (webSiteService == null) {
- return null;
- }
-
- Task getSubscriptionsAsyncTask = (Task)((IAzureWebSitesService)webSiteService).GetSubscriptionsAsync();
- await getSubscriptionsAsyncTask;
-
- IEnumerable