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 subscriptions = ((Task>)getSubscriptionsAsyncTask).Result; - object subscription = subscriptions.FirstOrDefault((object sub) => ((IAzureSubscription)sub).SubscriptionId == webSiteInfo.SubscriptionId); - if (subscription == null) { - return null; - } - - Task getResourcesAsyncTask = (Task)((IAzureSubscription)subscription).GetResourcesAsync(false); - await getResourcesAsyncTask; - - IEnumerable resources = ((Task>)getResourcesAsyncTask).Result; - object webSite = resources.FirstOrDefault((object res) => { - IAzureWebSite ws = res as IAzureWebSite; - if (ws == null) { - return false; - } - Uri browseUri; - Uri.TryCreate(ws.BrowseURL, UriKind.Absolute, out browseUri); - return browseUri != null && browseUri.Equals(webSiteInfo.Uri); - }); - if (webSite == null) { - return null; - } - - // Prepare a web request to get the publish settings. - // See http://msdn.microsoft.com/en-us/library/windowsazure/dn166996.aspx - string requestPath = string.Format( - "{0}/services/WebSpaces/{1}/sites/{2}/publishxml", - ((IAzureSubscription)subscription).SubscriptionId, - ((IAzureWebSite)webSite).WebSpace, - ((IAzureWebSite)webSite).Name); - Uri requestUri = new Uri(((IAzureSubscription)subscription).ServiceManagementEndpointUri, requestPath); - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(requestUri); - request.Method = "GET"; - request.ContentType = "application/xml"; - request.Headers.Add("x-ms-version", "2010-10-28"); - - // Set up authentication for the request, depending on whether the associated subscription context is - // account-based or certificate-based. - object context = ((IAzureSubscription)subscription).AzureCredentials; - if (context is IAzureAuthenticationCertificateSubscriptionContext) { - X509Certificate2 cert = await ((IAzureAuthenticationCertificateSubscriptionContext)context).AuthenticationCertificate.GetCertificateFromStoreAsync(); - request.ClientCertificates.Add(cert); - } else if (context is IAzureUserAccountSubscriptionContext) { - string authHeader = await ((IAzureUserAccountSubscriptionContext)context).GetAuthenticationHeaderAsync(false); - request.Headers.Add(HttpRequestHeader.Authorization, authHeader); - } else { - return null; - } - - using (WebResponse response = await request.GetResponseAsync()) - using (Stream 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. - Stream xmlData = new MemoryStream(); - await stream.CopyToAsync(xmlData); - xmlData.Position = 0; - return XDocument.Load(xmlData); - } - } - - private unsafe void AttachDebugger(Uri uri) { - var dte = (EnvDTE.DTE)_serviceProvider.GetService(typeof(EnvDTE.DTE)); - var debugger = (EnvDTE90.Debugger3)dte.Debugger; - - var transports = debugger.Transports; - EnvDTE80.Transport transport = null; - for (int i = 1; i <= transports.Count; ++i) { - var t = transports.Item(i); - Guid tid; - if (Guid.TryParse(t.ID, out tid) && tid == PythonRemoteDebugPortSupplier.PortSupplierGuid) { - transport = t; - } - } - if (transport == null) { - throw new InvalidOperationException("Python remote debugging transport is missing."); - } - - var processes = debugger.GetProcesses(transport, uri.ToString()); - if (processes.Count == 0) { - throw new InvalidOperationException("No Python processes found on remote host."); - } - - foreach (EnvDTE.Process process in processes) { - process.Attach(); - } - } - - /// /// Information about an Azure Web Site node in Server Explorer. /// diff --git a/Python/Product/PythonTools/PythonToolsPackage.cs b/Python/Product/PythonTools/PythonToolsPackage.cs index 596f7d409e..0af73ffce1 100644 --- a/Python/Product/PythonTools/PythonToolsPackage.cs +++ b/Python/Product/PythonTools/PythonToolsPackage.cs @@ -595,16 +595,10 @@ protected override void Initialize() { new ShowNativePythonFrames(this), new UsePythonStepping(this), #endif - }, GuidList.guidPythonToolsCmdSet); - #if FEATURE_AZURE_REMOTE_DEBUG - try { - RegisterCommands(new Command[] { - new AzureExplorerAttachDebuggerCommand(this) - }, GuidList.guidPythonToolsCmdSet); - } catch (NotSupportedException) { - } + new AzureExplorerAttachDebuggerCommand(this), #endif + }, GuidList.guidPythonToolsCmdSet); RegisterCommands(GetReplCommands(), GuidList.guidPythonToolsCmdSet); diff --git a/Python/products.settings b/Python/products.settings index 001ace001b..870ef9fdf3 100644 --- a/Python/products.settings +++ b/Python/products.settings @@ -18,8 +18,6 @@ false - $(ReleaseBuild) - true - false + true