Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Select tools path relative to the current devenv.exe #1012

Merged
merged 7 commits into from Jul 2, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -38,11 +38,6 @@ public interface IToolsPathProvider
/// </summary>
string GetMsbuildPath();

/// <summary>
/// Returns the path to the msdeploy.exe to use to deploy apps.
/// </summary>
string GetMsdeployPath();

/// <summary>
/// Returns the path to Visual Studio Remote Debugger tools.
/// </summary>
Expand Down
@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props" Condition="Exists('..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{114F9D92-CC07-47B9-A402-4D5A41572E4E}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>GoogleCloudExtension.PowerShellUtils.Tests</RootNamespace>
<AssemblyName>GoogleCloudExtension.PowerShellUtils.Tests</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">15.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<ReferencePath>$(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages</ReferencePath>
<IsCodedUITest>False</IsCodedUITest>
<TestProjectType>UnitTest</TestProjectType>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll</HintPath>
</Reference>
<Reference Include="Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\MSTest.TestFramework.1.2.1\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Management.Automation.dll.10.0.10586.0\lib\net40\System.Management.Automation.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="RemotePowerShellUtilsTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="..\GoogleCloudExtension.PowerShellUtils\Resources\InstallRemoteTool.ps1">
<Link>Resources\InstallRemoteTool.ps1</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\GoogleCloudExtension.PowerShellUtils\GoogleCloudExtension.PowerShellUtils.csproj">
<Project>{D94A10B2-5ADC-4708-AE7B-49B70381AB91}</Project>
<Name>GoogleCloudExtension.PowerShellUtils</Name>
</ProjectReference>
<ProjectReference Include="..\TestingHelpers\TestingHelpers.csproj">
<Project>{4EB5A3A0-3D68-4880-8855-405A46CDCE0B}</Project>
<Name>TestingHelpers</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets" Condition="Exists('$(VSToolsPath)\TeamTest\Microsoft.TestTools.targets')" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.props'))" />
<Error Condition="!Exists('..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets'))" />
</Target>
<Import Project="..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets" Condition="Exists('..\packages\MSTest.TestAdapter.1.2.1\build\net45\MSTest.TestAdapter.targets')" />
</Project>
@@ -0,0 +1,20 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

[assembly: AssemblyTitle("GoogleCloudExtension.PowerShellUtils.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("GoogleCloudExtension.PowerShellUtils.Tests")]
[assembly: AssemblyCopyright("Copyright © 2018")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

[assembly: ComVisible(false)]

[assembly: Guid("114f9d92-cc07-47b9-a402-4d5a41572e4e")]

// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,166 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header text is missing

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

using System;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;

namespace GoogleCloudExtension.PowerShellUtils.Tests
{
[TestClass]
[DeploymentItem(InstallerPsFilePath, "Resources")]
[SuppressMessage("ReSharper", "AccessToDisposedClosure")]
public class RemotePowerShellUtilsTests
{
private const string InstallerPsFilePath = "Resources/InstallRemoteTool.ps1";
private const string InstallerPsResourcePath = RemoteToolInstaller.InstallerPsFilePath;
private const string DefaultUser = "DefaultUser";
private const string DefaultPassword = "DefaultPassword";

public TestContext TestContext { get; set; }

[TestMethod]
public void TestGetEmbeddedFile_ThrowsForUnknownFile()
{
const string expectedResourceName = "Unknown/File.txt";
var e = Assert.ThrowsException<FileNotFoundException>(() => RemotePowerShellUtils.GetEmbeddedFile(expectedResourceName));
Assert.AreEqual(expectedResourceName, e.Message);
}

[TestMethod]
public void TestGetEmbeddedFile_GetFile()
{
string result = RemotePowerShellUtils.GetEmbeddedFile(InstallerPsResourcePath);

Assert.AreEqual(File.ReadAllText(InstallerPsFilePath), result);
}

[TestMethod]
[DataRow(null)]
[DataRow("")]
[DataRow(" \r\n\t\f")]
public void TestCreatePSCredential_ThrowsForInvalidPassword(string invalidPassword)
{
var e = Assert.ThrowsException<ArgumentException>(
() => RemotePowerShellUtils.CreatePSCredential(DefaultUser, invalidPassword));

Assert.AreEqual("input", e.Message);
}

[TestMethod]
public void TestCreatePSCredential_CreatesCredentialWithGivenUser()
{
const string expectedUser = "ExpectedUser";
PSCredential result = RemotePowerShellUtils.CreatePSCredential(expectedUser, DefaultPassword);
Assert.AreEqual(expectedUser, result.UserName);
}

[TestMethod]
public void TestCreatePSCredential_CreatesCredentialWithGivenPassword()
{
const string expectedPassword = "ExpectedPassword";
PSCredential result = RemotePowerShellUtils.CreatePSCredential(DefaultUser, expectedPassword);
Assert.AreEqual(expectedPassword.Length, result.Password.Length);
}

[TestMethod]
public async Task TestInvokeAsync_ThrowsOperationCanceledWhenTokenAlreadyCanceled()
{
using (PowerShell powerShell = PowerShell.Create())
{
await Assert.ThrowsExceptionAsync<OperationCanceledException>(
() => powerShell.InvokeAsync(new CancellationToken(true)));
}
}

[TestMethod]
public async Task TestInvokeAsync_DoesNotStartPowershellWhenTokenAlreadyCanceled()
{
using (PowerShell powerShell = PowerShell.Create())
{
await Assert.ThrowsExceptionAsync<OperationCanceledException>(
() => powerShell.InvokeAsync(new CancellationToken(true)));

Assert.AreEqual(PSInvocationState.NotStarted, powerShell.InvocationStateInfo.State);
}
}

[TestMethod]
[Timeout(5000)]
public async Task TestInvokeAsync_ThrowsTaskCanceledExceptionWhenTokenCanceled()
{
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddScript("Start-Sleep -Seconds 30");

await Assert.ThrowsExceptionAsync<OperationCanceledException>(
() => powerShell.InvokeAsync(new CancellationTokenSource(16).Token));
}
}

[TestMethod]
[Timeout(5000)]
public async Task TestInvokeAsync_StopsPowershellWhenTokenCanceled()
{
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddScript("Start-Sleep -Seconds 30");

await Assert.ThrowsExceptionAsync<OperationCanceledException>(
() => powerShell.InvokeAsync(new CancellationTokenSource(16).Token));

Assert.AreEqual(PSInvocationState.Stopped, powerShell.InvocationStateInfo.State);
}
}

[TestMethod]
public async Task TestInvokeAsync_ExecutesPowerShell()
{
const string expectedValue = "expectedValue";
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddVariable("varName", expectedValue);
powerShell.AddScript("Write-Output $varName");

PSDataCollection<PSObject> results = await powerShell.InvokeAsync();

Assert.AreEqual(expectedValue, results.Single().BaseObject);
}
}

[TestMethod]
public async Task TestInvokeAsync_ThrowsThrownPowerShellException()
{
const string expectedValue = "Test Exception";
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddVariable("varName", expectedValue);
powerShell.AddScript("throw $varName");

RuntimeException e =
await Assert.ThrowsExceptionAsync<RuntimeException>(() => powerShell.InvokeAsync());

Assert.AreEqual(expectedValue, e.ErrorRecord.Exception.Message);
}
}

[TestMethod]
public async Task TestInvokeAsync_ThrowsPowerShellErrorOnErrorActionStop()
{
const string expectedValue = "Test Exception";
using (PowerShell powerShell = PowerShell.Create())
{
powerShell.AddVariable("varName", expectedValue);
powerShell.AddScript("$ErrorActionPreference = \"Stop\"");
powerShell.AddScript("Write-Error $varName");

ActionPreferenceStopException e =
await Assert.ThrowsExceptionAsync<ActionPreferenceStopException>(() => powerShell.InvokeAsync());

Assert.AreEqual(expectedValue, e.ErrorRecord.Exception.Message);
}
}
}
}
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="MSTest.TestAdapter" version="1.2.1" targetFramework="net461" />
<package id="MSTest.TestFramework" version="1.2.1" targetFramework="net461" />
<package id="System.Management.Automation.dll" version="10.0.10586.0" targetFramework="net461" />
</packages>
Expand Up @@ -42,6 +42,9 @@
<Reference Include="System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\System.Management.Automation.dll.10.0.10586.0\lib\net40\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.ValueTuple, Version=4.0.3.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51, processorArchitecture=MSIL">
<HintPath>..\packages\System.ValueTuple.4.5.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
</Reference>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, I had used a C# 7 value tuple, but I removed it before the final change. This nuget package enables that feature, and is no longer needed. Deleted.

<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
Expand Down
Expand Up @@ -14,10 +14,11 @@

using System;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Security;
using System.Threading;
using System.Threading.Tasks;

namespace GoogleCloudExtension.PowerShellUtils
{
Expand All @@ -37,14 +38,14 @@ public static class RemotePowerShellUtils
/// <exception cref="FileNotFoundException">The file of <paramref name="resourceName"/> is not found.</exception>
public static string GetEmbeddedFile(string resourceName)
{
var assembly = Assembly.GetExecutingAssembly();
Assembly assembly = Assembly.GetExecutingAssembly();
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
if (stream == null)
{
throw new FileNotFoundException(resourceName);
}
using (StreamReader reader = new StreamReader(stream))
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
Expand Down Expand Up @@ -75,12 +76,47 @@ public static void AddVariable(this PowerShell powerShell, string name, object v
/// </summary>
private static SecureString ConvertToSecureString(string input)
{
if (String.IsNullOrWhiteSpace(input))
if (string.IsNullOrWhiteSpace(input))
{
throw new ArgumentException(nameof(input));
}
SecureString output = new SecureString();
input?.ToCharArray().ToList().ForEach(p => output.AppendChar(p));
var output = new SecureString();
foreach (char p in input)
{
output.AppendChar(p);
}

return output;
}

/// <summary>
/// Asyncronously executes the PowerShell commands.
/// </summary>
/// <param name="powerShell">The <seealso cref="PowerShell"/> object.</param>
/// <param name="cancelToken">When cancelation is requested, this method stops the execution and throws.</param>
/// <returns>
/// The <see cref="PSDataCollection{T}"/> returned by the powershell execution.
/// </returns>
public static async Task<PSDataCollection<PSObject>> InvokeAsync(
this PowerShell powerShell,
CancellationToken cancelToken = default(CancellationToken))
{
cancelToken.ThrowIfCancellationRequested();
Task<PSDataCollection<PSObject>> powershellTask =
Task.Factory.FromAsync(powerShell.BeginInvoke(), powerShell.EndInvoke);
cancelToken.Register(powerShell.Stop);

PSDataCollection<PSObject> output;
try
{
output = await powershellTask;
}
catch (Exception e) when (cancelToken.IsCancellationRequested)
{
throw new OperationCanceledException("PowerShell operation canceled", e, cancelToken);
}
// PowerShell can sometimes complete without error if the cancellation happend quickly enough.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: happened

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

cancelToken.ThrowIfCancellationRequested();
return output;
}
}
Expand Down