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
Changes from 5 commits
2747535
885ffe3
5e2c331
38214c2
38f7ba8
710767b
ad086c4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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")] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
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); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this needed? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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" /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
{ | ||
|
@@ -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(); | ||
} | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: happened There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
cancelToken.ThrowIfCancellationRequested(); | ||
return output; | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Header text is missing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.